Merge remote-tracking branch 'upstream/master' into devel
commit
8c5f34a979
|
@ -0,0 +1,14 @@
|
|||
# EditorConfig is awesome: http://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# 4 space - Tab indentation
|
||||
[*.{java,xml,js,html}]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
|
@ -1,10 +1,7 @@
|
|||
language: java
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
- oraclejdk7
|
||||
- openjdk7
|
||||
sudo: false
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# MITREid Connect
|
||||
---
|
||||
|
||||
[](https://maven-badges.herokuapp.com/maven-central/org.mitre/openid-connect-parent) [](https://travis-ci.org/mitreid-connect/OpenID-Connect-Java-Spring-Server)
|
||||
|
||||
此项目提供了一个业经认证的、用Java语言构筑于Spring平台之上的OpenID Connect参考实现,包括 [服务器端的实现库](openid-connect-server), [可部署的服务器包](openid-connect-server-webapp), [客户端 (RP) 的库](openid-connect-client), 以及 [工具类库](openid-connect-common)。该服务器可以用做OpenID Connect身份提供者,也可以用做一般意义上的OAuth 2.0授权服务器。
|
||||
|
||||
[](https://openid.net/certification/)
|
||||
|
||||
有关项目的更多信息参见:
|
||||
|
||||
* [项目在GitHub上的主页 (及相关项目)](https://github.com/mitreid-connect/)
|
||||
* [完整的文档](https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/wiki)
|
||||
* [Maven文档及Java API](http://mitreid-connect.github.com/)
|
||||
* [问题(Issue)追踪系统 (用于报告bug及提交支持请求)](https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/issues)
|
||||
* 项目的邮件列表: `mitreid-connect@mit.edu`, 及其 [在线存档](https://mailman.mit.edu/mailman/listinfo/mitreid-connect).
|
||||
|
||||
|
||||
项目的作者及主要贡献者有:
|
||||
|
||||
* [Justin Richer](https://github.com/jricher/)
|
||||
* [Amanda Anganes](https://github.com/aanganes/)
|
||||
* [Michael Jett](https://github.com/jumbojett/)
|
||||
* [Michael Walsh](https://github.com/nemonik/)
|
||||
* [Steve Moore](https://github.com/srmoore)
|
||||
* [Mike Derryberry](https://github.com/mtderryberry)
|
||||
* [William Kim](https://github.com/wikkim)
|
||||
* [Mark Janssen](https://github.com/praseodym)
|
||||
|
||||
|
||||
项目的中文译者:
|
||||
|
||||
* [刘晓曦](https://github.com/liouxiao/)
|
||||
|
||||
|
||||
|
||||
|
||||
版权所有 ©2016, [ MITRE公司 ](http://www.mitre.org/)
|
||||
以及 [MIT因特网信任联盟](http://www.mit-trust.org/). 采用Apache 2.0许可证, 详见 `LICENSE.txt`.
|
|
@ -21,7 +21,7 @@
|
|||
<parent>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<groupId>org.mitre</groupId>
|
||||
<version>1.2.7.cnaf-SNAPSHOT</version>
|
||||
<version>1.3.0.cnaf-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>openid-connect-client</artifactId>
|
||||
|
|
|
@ -23,6 +23,9 @@ import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.SECRET_JWT;
|
|||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
|
@ -40,6 +43,7 @@ import org.apache.http.impl.client.HttpClientBuilder;
|
|||
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
|
||||
import org.mitre.jwt.signer.service.impl.JWKSetCacheService;
|
||||
import org.mitre.jwt.signer.service.impl.SymmetricKeyJWTValidatorCacheService;
|
||||
import org.mitre.oauth2.model.PKCEAlgorithm;
|
||||
import org.mitre.oauth2.model.RegisteredClient;
|
||||
import org.mitre.openid.connect.client.model.IssuerServiceResponse;
|
||||
import org.mitre.openid.connect.client.service.AuthRequestOptionsService;
|
||||
|
@ -75,6 +79,7 @@ import com.nimbusds.jose.Algorithm;
|
|||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.JWSHeader;
|
||||
import com.nimbusds.jose.util.Base64;
|
||||
import com.nimbusds.jose.util.Base64URL;
|
||||
import com.nimbusds.jwt.JWT;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.JWTParser;
|
||||
|
@ -90,10 +95,11 @@ import com.nimbusds.jwt.SignedJWT;
|
|||
public class OIDCAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
|
||||
|
||||
protected final static String REDIRECT_URI_SESION_VARIABLE = "redirect_uri";
|
||||
protected final static String CODE_VERIFIER_SESSION_VARIABLE = "code_verifier";
|
||||
protected final static String STATE_SESSION_VARIABLE = "state";
|
||||
protected final static String NONCE_SESSION_VARIABLE = "nonce";
|
||||
protected final static String ISSUER_SESSION_VARIABLE = "issuer";
|
||||
protected static final String TARGET_SESSION_VARIABLE = "target";
|
||||
protected final static String TARGET_SESSION_VARIABLE = "target";
|
||||
protected final static int HTTP_SOCKET_TIMEOUT = 30000;
|
||||
|
||||
public final static String FILTER_PROCESSES_URL = "/openid_connect_login";
|
||||
|
@ -262,6 +268,26 @@ public class OIDCAuthenticationFilter extends AbstractAuthenticationProcessingFi
|
|||
String state = createState(session);
|
||||
|
||||
Map<String, String> options = authOptions.getOptions(serverConfig, clientConfig, request);
|
||||
|
||||
// if we're using PKCE, handle the challenge here
|
||||
if (clientConfig.getCodeChallengeMethod() != null) {
|
||||
String codeVerifier = createCodeVerifier(session);
|
||||
options.put("code_challenge_method", clientConfig.getCodeChallengeMethod().getName());
|
||||
if (clientConfig.getCodeChallengeMethod().equals(PKCEAlgorithm.plain)) {
|
||||
options.put("code_challenge", codeVerifier);
|
||||
} else if (clientConfig.getCodeChallengeMethod().equals(PKCEAlgorithm.S256)) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
String hash = Base64URL.encode(digest.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII))).toString();
|
||||
options.put("code_challenge", hash);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
String authRequest = authRequestBuilder.buildAuthRequestUrl(serverConfig, clientConfig, redirectUri, nonce, state, options, issResp.getLoginHint());
|
||||
|
||||
|
@ -302,6 +328,11 @@ public class OIDCAuthenticationFilter extends AbstractAuthenticationProcessingFi
|
|||
form.add("grant_type", "authorization_code");
|
||||
form.add("code", authorizationCode);
|
||||
form.setAll(authOptions.getTokenOptions(serverConfig, clientConfig, request));
|
||||
|
||||
String codeVerifier = getStoredCodeVerifier(session);
|
||||
if (codeVerifier != null) {
|
||||
form.add("code_verifier", codeVerifier);
|
||||
}
|
||||
|
||||
String redirectUri = getStoredSessionString(session, REDIRECT_URI_SESION_VARIABLE);
|
||||
if (redirectUri != null) {
|
||||
|
@ -675,6 +706,26 @@ public class OIDCAuthenticationFilter extends AbstractAuthenticationProcessingFi
|
|||
protected static String getStoredState(HttpSession session) {
|
||||
return getStoredSessionString(session, STATE_SESSION_VARIABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a random code challenge and store it in the session
|
||||
* @param session
|
||||
* @return
|
||||
*/
|
||||
protected static String createCodeVerifier(HttpSession session) {
|
||||
String challenge = new BigInteger(50, new SecureRandom()).toString(16);
|
||||
session.setAttribute(CODE_VERIFIER_SESSION_VARIABLE, challenge);
|
||||
return challenge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the stored challenge from our session
|
||||
* @param session
|
||||
* @return
|
||||
*/
|
||||
protected static String getStoredCodeVerifier(HttpSession session) {
|
||||
return getStoredSessionString(session, CODE_VERIFIER_SESSION_VARIABLE);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<parent>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<groupId>org.mitre</groupId>
|
||||
<version>1.2.7.cnaf-SNAPSHOT</version>
|
||||
<version>1.3.0.cnaf-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>openid-connect-common</artifactId>
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2016 The MITRE Corporation
|
||||
* and the MIT Internet Trust Consortium
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*******************************************************************************/
|
||||
|
||||
package org.mitre.jwt.assertion;
|
||||
|
||||
import com.nimbusds.jwt.JWT;
|
||||
|
||||
/**
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
public interface AssertionValidator {
|
||||
|
||||
public boolean isValid(JWT assertion);
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2016 The MITRE Corporation
|
||||
* and the MIT Internet Trust Consortium
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*******************************************************************************/
|
||||
|
||||
package org.mitre.jwt.assertion.impl;
|
||||
|
||||
import org.mitre.jwt.assertion.AssertionValidator;
|
||||
|
||||
import com.nimbusds.jwt.JWT;
|
||||
|
||||
/**
|
||||
* Reject all assertions passed in.
|
||||
*
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
public class NullAssertionValidator implements AssertionValidator {
|
||||
|
||||
/**
|
||||
* Reject all assertions passed in, always returns false.
|
||||
*/
|
||||
@Override
|
||||
public boolean isValid(JWT assertion) {
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2016 The MITRE Corporation
|
||||
* and the MIT Internet Trust Consortium
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*******************************************************************************/
|
||||
|
||||
package org.mitre.jwt.assertion.impl;
|
||||
|
||||
import java.text.ParseException;
|
||||
|
||||
import org.mitre.jwt.assertion.AssertionValidator;
|
||||
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
|
||||
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.nimbusds.jwt.JWT;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
|
||||
/**
|
||||
* Validates all assertions generated by this server
|
||||
*
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
public class SelfAssertionValidator implements AssertionValidator {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(SelfAssertionValidator.class);
|
||||
|
||||
@Autowired
|
||||
private ConfigurationPropertiesBean config;
|
||||
|
||||
@Autowired
|
||||
private JWTSigningAndValidationService jwtService;
|
||||
|
||||
@Override
|
||||
public boolean isValid(JWT assertion) {
|
||||
if (!(assertion instanceof SignedJWT)) {
|
||||
// unsigned assertion
|
||||
return false;
|
||||
}
|
||||
|
||||
JWTClaimsSet claims;
|
||||
try {
|
||||
claims = assertion.getJWTClaimsSet();
|
||||
} catch (ParseException e) {
|
||||
logger.debug("Invalid assertion claims");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Strings.isNullOrEmpty(claims.getIssuer())) {
|
||||
logger.debug("No issuer for assertion, rejecting");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (claims.getIssuer().equals(config.getIssuer())) {
|
||||
logger.debug("Issuer is not the same as this server, rejecting");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (jwtService.validateSignature((SignedJWT) assertion)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2016 The MITRE Corporation
|
||||
* and the MIT Internet Trust Consortium
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*******************************************************************************/
|
||||
|
||||
package org.mitre.jwt.assertion.impl;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.mitre.jwt.assertion.AssertionValidator;
|
||||
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
|
||||
import org.mitre.jwt.signer.service.impl.JWKSetCacheService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.nimbusds.jwt.JWT;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
|
||||
/**
|
||||
* Checks to see if the assertion was signed by a particular authority available from a whitelist
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
public class WhitelistedIssuerAssertionValidator implements AssertionValidator {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(WhitelistedIssuerAssertionValidator.class);
|
||||
|
||||
/**
|
||||
* Map of issuer -> JWKSetUri
|
||||
*/
|
||||
private Map<String, String> whitelist = new HashMap<>();
|
||||
|
||||
/**
|
||||
* @return the whitelist
|
||||
*/
|
||||
public Map<String, String> getWhitelist() {
|
||||
return whitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param whitelist the whitelist to set
|
||||
*/
|
||||
public void setWhitelist(Map<String, String> whitelist) {
|
||||
this.whitelist = whitelist;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private JWKSetCacheService jwkCache;
|
||||
|
||||
@Override
|
||||
public boolean isValid(JWT assertion) {
|
||||
|
||||
if (!(assertion instanceof SignedJWT)) {
|
||||
// unsigned assertion
|
||||
return false;
|
||||
}
|
||||
|
||||
JWTClaimsSet claims;
|
||||
try {
|
||||
claims = assertion.getJWTClaimsSet();
|
||||
} catch (ParseException e) {
|
||||
logger.debug("Invalid assertion claims");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Strings.isNullOrEmpty(claims.getIssuer())) {
|
||||
logger.debug("No issuer for assertion, rejecting");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!whitelist.containsKey(claims.getIssuer())) {
|
||||
logger.debug("Issuer is not in whitelist, rejecting");
|
||||
return false;
|
||||
}
|
||||
|
||||
String jwksUri = whitelist.get(claims.getIssuer());
|
||||
|
||||
JWTSigningAndValidationService validator = jwkCache.getValidator(jwksUri);
|
||||
|
||||
if (validator.validateSignature((SignedJWT) assertion)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -51,14 +51,18 @@ import org.mitre.oauth2.model.convert.JWEAlgorithmStringConverter;
|
|||
import org.mitre.oauth2.model.convert.JWEEncryptionMethodStringConverter;
|
||||
import org.mitre.oauth2.model.convert.JWKSetStringConverter;
|
||||
import org.mitre.oauth2.model.convert.JWSAlgorithmStringConverter;
|
||||
import org.mitre.oauth2.model.convert.JWTStringConverter;
|
||||
import org.mitre.oauth2.model.convert.PKCEAlgorithmStringConverter;
|
||||
import org.mitre.oauth2.model.convert.SimpleGrantedAuthorityStringConverter;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.provider.ClientDetails;
|
||||
|
||||
import com.nimbusds.jose.Algorithm;
|
||||
import com.nimbusds.jose.EncryptionMethod;
|
||||
import com.nimbusds.jose.JWEAlgorithm;
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jwt.JWT;
|
||||
|
||||
/**
|
||||
* @author jricher
|
||||
|
@ -144,6 +148,12 @@ public class ClientDetailsEntity implements ClientDetails {
|
|||
|
||||
/** fields for UMA */
|
||||
private Set<String> claimsRedirectUris;
|
||||
|
||||
/** Software statement **/
|
||||
private JWT softwareStatement;
|
||||
|
||||
/** PKCE **/
|
||||
private PKCEAlgorithm codeChallengeMethod;
|
||||
|
||||
public enum AuthMethod {
|
||||
SECRET_POST("client_secret_post"),
|
||||
|
@ -226,7 +236,7 @@ public class ClientDetailsEntity implements ClientDetails {
|
|||
return lookup.get(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a blank ClientDetailsEntity
|
||||
*/
|
||||
|
@ -988,4 +998,38 @@ public class ClientDetailsEntity implements ClientDetails {
|
|||
this.claimsRedirectUris = claimsRedirectUris;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the softwareStatement
|
||||
*/
|
||||
@Basic
|
||||
@Column(name = "software_statement")
|
||||
@Convert(converter = JWTStringConverter.class)
|
||||
public JWT getSoftwareStatement() {
|
||||
return softwareStatement;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param softwareStatement the softwareStatement to set
|
||||
*/
|
||||
public void setSoftwareStatement(JWT softwareStatement) {
|
||||
this.softwareStatement = softwareStatement;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the codeChallengeMethod
|
||||
*/
|
||||
@Basic
|
||||
@Column(name = "code_challenge_method")
|
||||
@Convert(converter = PKCEAlgorithmStringConverter.class)
|
||||
public PKCEAlgorithm getCodeChallengeMethod() {
|
||||
return codeChallengeMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param codeChallengeMethod the codeChallengeMethod to set
|
||||
*/
|
||||
public void setCodeChallengeMethod(PKCEAlgorithm codeChallengeMethod) {
|
||||
this.codeChallengeMethod = codeChallengeMethod;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2016 The MITRE Corporation
|
||||
* and the MIT Internet Trust Consortium
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*******************************************************************************/
|
||||
|
||||
package org.mitre.oauth2.model;
|
||||
|
||||
import com.nimbusds.jose.Algorithm;
|
||||
import com.nimbusds.jose.Requirement;
|
||||
|
||||
/**
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
public final class PKCEAlgorithm extends Algorithm {
|
||||
|
||||
public static final PKCEAlgorithm plain = new PKCEAlgorithm("plain", Requirement.REQUIRED);
|
||||
|
||||
public static final PKCEAlgorithm S256 = new PKCEAlgorithm("S256", Requirement.OPTIONAL);
|
||||
|
||||
public PKCEAlgorithm(String name, Requirement req) {
|
||||
super(name, req);
|
||||
}
|
||||
|
||||
public PKCEAlgorithm(String name) {
|
||||
super(name, null);
|
||||
}
|
||||
|
||||
public static PKCEAlgorithm parse(final String s) {
|
||||
if (s.equals(plain.getName())) {
|
||||
return plain;
|
||||
} else if (s.equals(S256.getName())) {
|
||||
return S256;
|
||||
} else {
|
||||
return new PKCEAlgorithm(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -33,6 +33,7 @@ import com.nimbusds.jose.EncryptionMethod;
|
|||
import com.nimbusds.jose.JWEAlgorithm;
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jwt.JWT;
|
||||
|
||||
/**
|
||||
* @author jricher
|
||||
|
@ -799,6 +800,38 @@ public class RegisteredClient {
|
|||
client.setClaimsRedirectUris(claimsRedirectUris);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @see org.mitre.oauth2.model.ClientDetailsEntity#getSoftwareStatement()
|
||||
*/
|
||||
public JWT getSoftwareStatement() {
|
||||
return client.getSoftwareStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param softwareStatement
|
||||
* @see org.mitre.oauth2.model.ClientDetailsEntity#setSoftwareStatement(com.nimbusds.jwt.JWT)
|
||||
*/
|
||||
public void setSoftwareStatement(JWT softwareStatement) {
|
||||
client.setSoftwareStatement(softwareStatement);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @see org.mitre.oauth2.model.ClientDetailsEntity#getCodeChallengeMethod()
|
||||
*/
|
||||
public PKCEAlgorithm getCodeChallengeMethod() {
|
||||
return client.getCodeChallengeMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param codeChallengeMethod
|
||||
* @see org.mitre.oauth2.model.ClientDetailsEntity#setCodeChallengeMethod(org.mitre.oauth2.model.PKCEAlgorithm)
|
||||
*/
|
||||
public void setCodeChallengeMethod(PKCEAlgorithm codeChallengeMethod) {
|
||||
client.setCodeChallengeMethod(codeChallengeMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the src
|
||||
*/
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.mitre.oauth2.model;
|
||||
|
||||
public interface RegisteredClientFields {
|
||||
public String SOFTWARE_STATEMENT = "software_statement";
|
||||
public String CLAIMS_REDIRECT_URIS = "claims_redirect_uris";
|
||||
public String CLIENT_SECRET_EXPIRES_AT = "client_secret_expires_at";
|
||||
public String CLIENT_ID_ISSUED_AT = "client_id_issued_at";
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2016 The MITRE Corporation
|
||||
* and the MIT Internet Trust Consortium
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*******************************************************************************/
|
||||
|
||||
package org.mitre.oauth2.model.convert;
|
||||
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
|
||||
import org.mitre.oauth2.model.PKCEAlgorithm;
|
||||
|
||||
/**
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
@Converter
|
||||
public class PKCEAlgorithmStringConverter implements AttributeConverter<PKCEAlgorithm, String> {
|
||||
|
||||
@Override
|
||||
public String convertToDatabaseColumn(PKCEAlgorithm attribute) {
|
||||
if (attribute != null) {
|
||||
return attribute.getName();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see javax.persistence.AttributeConverter#convertToEntityAttribute(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public PKCEAlgorithm convertToEntityAttribute(String dbData) {
|
||||
if (dbData != null) {
|
||||
return PKCEAlgorithm.parse(dbData);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -21,6 +21,7 @@ package org.mitre.openid.connect;
|
|||
|
||||
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.APPLICATION_TYPE;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.CLAIMS_REDIRECT_URIS;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID_ISSUED_AT;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_NAME;
|
||||
|
@ -47,9 +48,10 @@ import static org.mitre.oauth2.model.RegisteredClientFields.REQUEST_OBJECT_SIGNI
|
|||
import static org.mitre.oauth2.model.RegisteredClientFields.REQUEST_URIS;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.REQUIRE_AUTH_TIME;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.RESPONSE_TYPES;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.*;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.SCOPE;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.SCOPE_SEPARATOR;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.SECTOR_IDENTIFIER_URI;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.SOFTWARE_STATEMENT;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.SUBJECT_TYPE;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_METHOD;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_SIGNING_ALG;
|
||||
|
@ -67,6 +69,7 @@ import static org.mitre.util.JsonUtils.getAsStringSet;
|
|||
|
||||
import java.text.ParseException;
|
||||
|
||||
import org.mitre.jwt.assertion.AssertionValidator;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity.AppType;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
|
||||
|
@ -74,14 +77,19 @@ import org.mitre.oauth2.model.ClientDetailsEntity.SubjectType;
|
|||
import org.mitre.oauth2.model.RegisteredClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jwt.JWT;
|
||||
import com.nimbusds.jwt.JWTParser;
|
||||
|
||||
/**
|
||||
* Utility class to handle the parsing and serialization of ClientDetails objects.
|
||||
|
@ -94,7 +102,7 @@ public class ClientDetailsEntityJsonProcessor {
|
|||
private static Logger logger = LoggerFactory.getLogger(ClientDetailsEntityJsonProcessor.class);
|
||||
|
||||
private static JsonParser parser = new JsonParser();
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Create an unbound ClientDetailsEntity from the given JSON string.
|
||||
|
@ -148,6 +156,7 @@ public class ClientDetailsEntityJsonProcessor {
|
|||
c.setJwks(jwks);
|
||||
} catch (ParseException e) {
|
||||
logger.error("Unable to parse JWK Set for client", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -195,6 +204,18 @@ public class ClientDetailsEntityJsonProcessor {
|
|||
|
||||
c.setClaimsRedirectUris(getAsStringSet(o, CLAIMS_REDIRECT_URIS));
|
||||
|
||||
String softwareStatement = getAsString(o, SOFTWARE_STATEMENT);
|
||||
if (!Strings.isNullOrEmpty(softwareStatement)) {
|
||||
try {
|
||||
JWT softwareStatementJwt = JWTParser.parse(softwareStatement);
|
||||
c.setSoftwareStatement(softwareStatementJwt);
|
||||
} catch (ParseException e) {
|
||||
logger.warn("Error parsing software statement", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return c;
|
||||
} else {
|
||||
return null;
|
||||
|
@ -318,6 +339,10 @@ public class ClientDetailsEntityJsonProcessor {
|
|||
|
||||
o.add(CLAIMS_REDIRECT_URIS, getAsArray(c.getClaimsRedirectUris()));
|
||||
|
||||
if (c.getSoftwareStatement() != null) {
|
||||
o.addProperty(SOFTWARE_STATEMENT, c.getSoftwareStatement().serialize());
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,6 @@ import org.springframework.web.servlet.i18n.AbstractLocaleContextResolver;
|
|||
* @author jricher
|
||||
*
|
||||
*/
|
||||
@Component("localeResolver")
|
||||
public class ConfigurationBeanLocaleResolver extends AbstractLocaleContextResolver {
|
||||
|
||||
@Autowired
|
||||
|
|
|
@ -49,6 +49,8 @@ public class ConfigurationPropertiesBean {
|
|||
private String issuer;
|
||||
|
||||
private String topbarTitle;
|
||||
|
||||
private String shortTopbarTitle;
|
||||
|
||||
private String logoImageUrl;
|
||||
|
||||
|
@ -118,6 +120,17 @@ public class ConfigurationPropertiesBean {
|
|||
public void setTopbarTitle(String topbarTitle) {
|
||||
this.topbarTitle = topbarTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If shortTopbarTitle is undefined, returns topbarTitle.
|
||||
*/
|
||||
public String getShortTopbarTitle() {
|
||||
return shortTopbarTitle == null ? topbarTitle : shortTopbarTitle;
|
||||
}
|
||||
|
||||
public void setShortTopbarTitle(String shortTopbarTitle) {
|
||||
this.shortTopbarTitle = shortTopbarTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the logoImageUrl
|
||||
|
|
|
@ -497,6 +497,9 @@ public class DefaultUserInfo implements UserInfo {
|
|||
ui.setEmail(nullSafeGetString(obj, "email"));
|
||||
ui.setEmailVerified(obj.has("email_verified") && obj.get("email_verified").isJsonPrimitive() ? obj.get("email_verified").getAsBoolean() : null);
|
||||
|
||||
ui.setPhoneNumber(nullSafeGetString(obj, "phone_number"));
|
||||
ui.setPhoneNumberVerified(obj.has("phone_number_verified") && obj.get("phone_number_verified").isJsonPrimitive() ? obj.get("phone_number_verified").getAsBoolean() : null);
|
||||
|
||||
if (obj.has("address") && obj.get("address").isJsonObject()) {
|
||||
JsonObject addr = obj.get("address").getAsJsonObject();
|
||||
ui.setAddress(new DefaultAddress());
|
||||
|
|
|
@ -134,5 +134,14 @@ public class ConfigurationPropertiesBeanTest {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShortTopbarTitle() {
|
||||
ConfigurationPropertiesBean bean = new ConfigurationPropertiesBean();
|
||||
bean.setTopbarTitle("LONG");
|
||||
assertEquals("LONG", bean.getShortTopbarTitle());
|
||||
bean.setShortTopbarTitle("SHORT");
|
||||
assertEquals("SHORT", bean.getShortTopbarTitle());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<parent>
|
||||
<groupId>org.mitre</groupId>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<version>1.2.7.cnaf-SNAPSHOT</version>
|
||||
<version>1.3.0.cnaf-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>openid-connect-server-webapp</artifactId>
|
||||
<packaging>war</packaging>
|
||||
|
@ -134,4 +134,5 @@
|
|||
<artifactId>HikariCP</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<description>Deployable package of the OpenID Connect server</description>
|
||||
</project>
|
||||
|
|
|
@ -169,6 +169,10 @@ CREATE TABLE IF NOT EXISTS client_details (
|
|||
initiate_login_uri VARCHAR(2048),
|
||||
clear_access_tokens_on_refresh BOOLEAN DEFAULT true NOT NULL,
|
||||
|
||||
software_statement VARCHAR(4096),
|
||||
|
||||
code_challenge_method VARCHAR(256),
|
||||
|
||||
UNIQUE (client_id)
|
||||
);
|
||||
|
||||
|
|
|
@ -198,13 +198,13 @@
|
|||
<bean id="clientAssertionAuthenticationProvider" class="org.mitre.openid.connect.assertion.JWTBearerAuthenticationProvider" />
|
||||
|
||||
<!-- Configure locale information -->
|
||||
<bean id="messageSource" class="org.mitre.openid.connect.config.JsonMessageSource">
|
||||
<property name="baseDirectory" value="/resources/js/locale/" />
|
||||
<property name="useCodeAsDefaultMessage" value="true" />
|
||||
</bean>
|
||||
<import resource="locale-config.xml" />
|
||||
|
||||
<!-- user services -->
|
||||
<import resource="user-context.xml" />
|
||||
|
||||
<!-- assertion processing -->
|
||||
<import resource="assertion-config.xml" />
|
||||
|
||||
<!-- End Spring Security configuration -->
|
||||
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright 2016 The MITRE Corporation
|
||||
and the MIT Internet Trust Consortium
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:security="http://www.springframework.org/schema/security"
|
||||
xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd
|
||||
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
|
||||
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
|
||||
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
|
||||
|
||||
<!-- validate incoming tokens for JWT assertions -->
|
||||
<bean id="jwtAssertionValidator" class="org.mitre.jwt.assertion.impl.NullAssertionValidator" />
|
||||
|
||||
<!-- translate incoming assertions to token authorization objects -->
|
||||
<bean id="jwtAssertionTokenFactory" class="org.mitre.oauth2.assertion.impl.DirectCopyRequestFactory" />
|
||||
|
||||
<!-- validate client software statements for dynamic registration -->
|
||||
<!-- <bean id="clientAssertionValidator" class="org.mitre.jwt.assertion.impl.NullAssertionValidator" /> -->
|
||||
|
||||
<!-- this class will pass assertions signed by the issuers and keys in the whitelist -->
|
||||
<bean id="clientAssertionValidator" class="org.mitre.jwt.assertion.impl.WhitelistedIssuerAssertionValidator">
|
||||
<property name="whitelist">
|
||||
<map>
|
||||
<entry key="http://artemesia.local" value="http://localhost:8080/openid-connect-server-webapp/jwk" />
|
||||
</map>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
|
||||
</beans>
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="messageSource" class="org.mitre.openid.connect.config.JsonMessageSource">
|
||||
<property name="baseDirectory" value="/resources/js/locale/" />
|
||||
<property name="useCodeAsDefaultMessage" value="true" />
|
||||
</bean>
|
||||
|
||||
<bean id="localeResolver" class="org.mitre.openid.connect.config.ConfigurationBeanLocaleResolver" />
|
||||
|
||||
</beans>
|
|
@ -34,7 +34,13 @@
|
|||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="brand" href=""><img src="${ config.logoImageUrl }" /> ${config.topbarTitle}</a>
|
||||
<a class="brand" href="">
|
||||
<img src="${ config.logoImageUrl }" />
|
||||
<span>
|
||||
<span class="visible-phone">${config.shortTopbarTitle}</span>
|
||||
<span class="hidden-phone">${config.topbarTitle}</span>
|
||||
</span>
|
||||
</a>
|
||||
<c:if test="${ not empty pageName }">
|
||||
<div class="nav-collapse collapse">
|
||||
<ul class="nav">
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
|
||||
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
|
||||
<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%>
|
||||
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags"%>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
|
||||
<%@ page import="org.springframework.security.core.AuthenticationException"%>
|
||||
<%@ page import="org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException"%>
|
||||
<%@ page import="org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter"%>
|
||||
|
@ -36,7 +37,7 @@
|
|||
</h1>
|
||||
|
||||
<form name="confirmationForm"
|
||||
action="<%=request.getContextPath()%>/authorize" method="post">
|
||||
action="${pageContext.request.contextPath.endsWith('/') ? pageContext.request.contextPath : pageContext.request.contextPath.concat('/') }authorize" method="post">
|
||||
|
||||
<div class="row">
|
||||
<div class="span5 offset1 well-small" style="text-align: left">
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
|
||||
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
|
||||
<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%>
|
||||
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags"%>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
|
||||
<%@page import="org.springframework.http.HttpStatus"%>
|
||||
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
|
||||
<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
|
||||
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
|
||||
<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%>
|
||||
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
|
||||
<%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags"%>
|
||||
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
|
||||
<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%>
|
||||
|
@ -30,18 +31,18 @@ $(document).ready(function() {
|
|||
<div>
|
||||
<div class="input-prepend input-block-level">
|
||||
<span class="add-on"><i class="icon-user"></i></span>
|
||||
<input type="text" placeholder="Username" autocorrect="off" autocapitalize="off" autocomplete="off" spellcheck="false" value="<c:out value="${ login_hint }" />" id="j_username" name="j_username">
|
||||
<input type="text" placeholder="<spring:message code="login.username"/>" autocorrect="off" autocapitalize="off" autocomplete="off" spellcheck="false" value="<c:out value="${ login_hint }" />" id="j_username" name="j_username">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="input-prepend input-block-level">
|
||||
<span class="add-on"><i class="icon-lock"></i></span>
|
||||
<input type="password" placeholder="Password" autocorrect="off" autocapitalize="off" autocomplete="off" spellcheck="false" id="j_password" name="j_password">
|
||||
<input type="password" placeholder="<spring:message code="login.password"/>" autocorrect="off" autocapitalize="off" autocomplete="off" spellcheck="false" id="j_password" name="j_password">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
|
||||
<input type="submit" class="btn" value="Login" name="submit">
|
||||
<input type="submit" class="btn" value="<spring:message code="login.login-button"/>" name="submit">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
|
||||
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
|
||||
<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%>
|
||||
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
|
||||
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
|
||||
<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%>
|
||||
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags"%>
|
||||
|
|
|
@ -192,3 +192,11 @@ h1,label {
|
|||
max-width: 64px;
|
||||
max-height: 64px
|
||||
}
|
||||
|
||||
/* Modal and sheet fight for the same z-index otherwise */
|
||||
.modal-backdrop {
|
||||
z-index: 2040;
|
||||
}
|
||||
.modal {
|
||||
z-index: 2050;
|
||||
}
|
||||
|
|
|
@ -94,22 +94,7 @@ var ListWidgetChildView = Backbone.View.extend({
|
|||
|
||||
this.model.destroy({
|
||||
dataType: false, processData: false,
|
||||
error:function (error, response) {
|
||||
console.log("An error occurred when deleting from a list widget");
|
||||
|
||||
//Pull out the response text.
|
||||
var responseJson = JSON.parse(response.responseText);
|
||||
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html(responseJson.error);
|
||||
$('#modalAlert div.modal-body').html(responseJson.error_description);
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
}
|
||||
error:app.errorHandlerView.handleError()
|
||||
});
|
||||
|
||||
},
|
||||
|
@ -366,6 +351,66 @@ var UserProfileView = Backbone.View.extend({
|
|||
}
|
||||
});
|
||||
|
||||
// error handler
|
||||
var ErrorHandlerView = Backbone.View.extend({
|
||||
|
||||
initialize:function(options) {
|
||||
this.options = options;
|
||||
if (!this.template) {
|
||||
this.template = _.template($('#tmpl-error-box').html());
|
||||
}
|
||||
if (!this.headerTemplate) {
|
||||
this.headerTemplate = _.template($('#tmpl-error-header').html());
|
||||
}
|
||||
},
|
||||
|
||||
reloadPage:function(event) {
|
||||
event.preventDefault();
|
||||
window.location.reload(true);
|
||||
},
|
||||
|
||||
handleError:function(message) {
|
||||
|
||||
if (!message) {
|
||||
message = {};
|
||||
}
|
||||
|
||||
if (message.log) {
|
||||
console.log(message.log);
|
||||
}
|
||||
|
||||
var _self = this;
|
||||
|
||||
return function(model, response, options) {
|
||||
|
||||
_self.showErrorMessage(
|
||||
_self.headerTemplate({message: message, model: model, response: response, options: options}),
|
||||
_self.template({message: message, model: model, response: response, options: options})
|
||||
);
|
||||
|
||||
$('#modalAlert .modal-body .page-reload').on('click', _self.reloadPage);
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
showErrorMessage:function(header, message) {
|
||||
// hide the sheet if it's visible
|
||||
$('#loadingbox').sheet('hide');
|
||||
|
||||
$('#modalAlert').i18n();
|
||||
$('#modalAlert div.modal-header').html(header);
|
||||
$('#modalAlert .modal-body').html(message);
|
||||
|
||||
$('#modalAlert').modal({
|
||||
'backdrop': 'static',
|
||||
'keyboard': true,
|
||||
'show': true
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Router
|
||||
var AppRouter = Backbone.Router.extend({
|
||||
|
||||
|
@ -424,6 +469,8 @@ var AppRouter = Backbone.Router.extend({
|
|||
});
|
||||
|
||||
this.breadCrumbView.render();
|
||||
|
||||
this.errorHandlerView = new ErrorHandlerView();
|
||||
|
||||
var base = $('base').attr('href');
|
||||
$.getJSON(base + '.well-known/openid-configuration', function(data) {
|
||||
|
@ -1048,9 +1095,10 @@ $(function () {
|
|||
});
|
||||
|
||||
window.onerror = function ( message, filename, lineno, colno, error ){
|
||||
console.log(message);
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html($.t('error.title'));
|
||||
$('#modalAlert div.modal-body').html($.t('error.message') + ' <br /> ' [filename, lineno, colno, error]);
|
||||
$('#modalAlert div.modal-body').html($.t('error.message') + message + ' <br /> ' + [filename, lineno, colno, error]);
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
|
|
|
@ -44,7 +44,7 @@ var BlackListListView = Backbone.View.extend({
|
|||
'<span class="label" id="loading-blacklist">' + $.t('admin.blacklist') + '</span> '
|
||||
);
|
||||
|
||||
$.when(this.collection.fetchIfNeeded({success:function(e) {$('#loading-blacklist').addClass('label-success');}}))
|
||||
$.when(this.collection.fetchIfNeeded({success:function(e) {$('#loading-blacklist').addClass('label-success');}, error:app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
$('#loadingbox').sheet('hide');
|
||||
callback();
|
||||
|
@ -121,20 +121,7 @@ var BlackListListView = Backbone.View.extend({
|
|||
_self.collection.add(item);
|
||||
_self.render();
|
||||
},
|
||||
error:function(error, response) {
|
||||
//Pull out the response text.
|
||||
var responseJson = JSON.parse(response.responseText);
|
||||
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html(responseJson.error);
|
||||
$('#modalAlert div.modal-body').html(responseJson.error_description);
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
}
|
||||
error:app.errorHandlerView.handleError()
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -182,23 +169,7 @@ var BlackListWidgetView = Backbone.View.extend({
|
|||
});
|
||||
});
|
||||
},
|
||||
error:function (model, response) {
|
||||
//Pull out the response text.
|
||||
var responseJson = {error: 'Error', error_description: 'Error.'};
|
||||
if (response) {
|
||||
responseJson = JSON.parse(response.responseText);
|
||||
}
|
||||
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html(responseJson.error);
|
||||
$('#modalAlert div.modal-body').html(responseJson.error_description);
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
}
|
||||
error:app.errorHandlerView.handleError()
|
||||
});
|
||||
|
||||
_self.parentView.delegateEvents();
|
||||
|
|
|
@ -286,15 +286,7 @@ var ClientView = Backbone.View.extend({
|
|||
console.log('token:' + token.get('value'));
|
||||
$('#modalAlert .modal-body #registrationToken').val(token.get('value'));
|
||||
},
|
||||
error: function() {
|
||||
$('#modalAlert').i18n();
|
||||
$('#modalAlert .modal-body').html($t('client.client-form.rotate-registration-token-error'));
|
||||
$('#modalAlert').modal({
|
||||
'backdrop': 'static',
|
||||
'keyboard': true,
|
||||
'show': true
|
||||
});
|
||||
}
|
||||
error: app.errorHandlerView.handleError({message: $.t('client.client-form.rotate-registration-token-error')})
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -307,16 +299,7 @@ var ClientView = Backbone.View.extend({
|
|||
});
|
||||
|
||||
},
|
||||
error:function() {
|
||||
$('#modalAlert').i18n();
|
||||
$('#modalAlert .modal-body').html($t('client.client-form.registration-token-error'));
|
||||
$('#modalAlert').modal({
|
||||
'backdrop': 'static',
|
||||
'keyboard': true,
|
||||
'show': true
|
||||
});
|
||||
|
||||
}
|
||||
error:app.errorHandlerView.handleError({log: "An error occurred when fetching the registration token", message: $.t('client.client-form.registration-token-error')})
|
||||
});
|
||||
|
||||
},
|
||||
|
@ -374,22 +357,7 @@ var ClientView = Backbone.View.extend({
|
|||
});
|
||||
});
|
||||
},
|
||||
error:function (error, response) {
|
||||
console.log("An error occurred when deleting a client");
|
||||
|
||||
//Pull out the response text.
|
||||
var responseJson = JSON.parse(response.responseText);
|
||||
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html(responseJson.error);
|
||||
$('#modalAlert div.modal-body').html(responseJson.error_description);
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
}
|
||||
error:app.errorHandlerView.handleError({log: "An error occurred when deleting a client"})
|
||||
});
|
||||
|
||||
_self.parentView.delegateEvents();
|
||||
|
@ -453,10 +421,10 @@ var ClientListView = Backbone.View.extend({
|
|||
'<span class="label" id="loading-stats">' + $.t("common.statistics") + '</span> '
|
||||
);
|
||||
|
||||
$.when(this.model.fetchIfNeeded({success:function(e) {$('#loading-clients').addClass('label-success');}}),
|
||||
this.options.whiteListList.fetchIfNeeded({success:function(e) {$('#loading-whitelist').addClass('label-success');}}),
|
||||
this.options.stats.fetchIfNeeded({success:function(e) {$('#loading-stats').addClass('label-success');}}),
|
||||
this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}}))
|
||||
$.when(this.model.fetchIfNeeded({success:function(e) {$('#loading-clients').addClass('label-success');}, error:app.errorHandlerView.handleError()}),
|
||||
this.options.whiteListList.fetchIfNeeded({success:function(e) {$('#loading-whitelist').addClass('label-success');}, error:app.errorHandlerView.handleError()}),
|
||||
this.options.stats.fetchIfNeeded({success:function(e) {$('#loading-stats').addClass('label-success');}, error:app.errorHandlerView.handleError()}),
|
||||
this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}, error:app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
$('#loadingbox').sheet('hide');
|
||||
callback();
|
||||
|
@ -567,10 +535,10 @@ var ClientListView = Backbone.View.extend({
|
|||
);
|
||||
|
||||
var _self = this;
|
||||
$.when(this.model.fetch({success:function(e) {$('#loading-clients').addClass('label-success');}}),
|
||||
this.options.whiteListList.fetch({success:function(e) {$('#loading-whitelist').addClass('label-success');}}),
|
||||
this.options.stats.fetch({success:function(e) {$('#loading-stats').addClass('label-success');}}),
|
||||
this.options.systemScopeList.fetch({success:function(e) {$('#loading-scopes').addClass('label-success');}}))
|
||||
$.when(this.model.fetch({success:function(e) {$('#loading-clients').addClass('label-success');}, error:app.errorHandlerView.handleError()}),
|
||||
this.options.whiteListList.fetch({success:function(e) {$('#loading-whitelist').addClass('label-success');}, error:app.errorHandlerView.handleError()}),
|
||||
this.options.stats.fetch({success:function(e) {$('#loading-stats').addClass('label-success');}, error:app.errorHandlerView.handleError()}),
|
||||
this.options.systemScopeList.fetch({success:function(e) {$('#loading-scopes').addClass('label-success');}, error:app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
$('#loadingbox').sheet('hide');
|
||||
_self.render();
|
||||
|
@ -667,8 +635,8 @@ var ClientFormView = Backbone.View.extend({
|
|||
'<span class="label" id="loading-scopes">' + $.t("common.scopes") + '</span> '
|
||||
);
|
||||
|
||||
$.when(this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}}),
|
||||
this.model.fetchIfNeeded({success:function(e) {$('#loading-clients').addClass('label-success');}}))
|
||||
$.when(this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}, error:app.errorHandlerView.handleError()}),
|
||||
this.model.fetchIfNeeded({success:function(e) {$('#loading-clients').addClass('label-success');}, error:app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
$('#loadingbox').sheet('hide');
|
||||
callback();
|
||||
|
@ -917,16 +885,8 @@ var ClientFormView = Backbone.View.extend({
|
|||
var sectorIdentifierUri = $('#sectorIdentifierUri input').val();
|
||||
if (subjectType == 'PAIRWISE' && redirectUris.length > 1 && sectorIdentifierUri == '') {
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html("Consistency error");
|
||||
$('#modalAlert div.modal-body').html("Pairwise identifiers cannot be used with multiple redirect URIs unless a sector identifier URI is also registered.");
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
|
||||
return false;
|
||||
app.errorHandlerView.showErrorMessage($.t("client.client-form.error.consistency"), $.t("client.client-form.error.pairwise-sector"));
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
|
@ -944,18 +904,8 @@ var ClientFormView = Backbone.View.extend({
|
|||
jwks = JSON.parse($('#jwks textarea').val());
|
||||
} catch (e) {
|
||||
console.log("An error occurred when parsing the JWK Set");
|
||||
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html("JWK Set Error");
|
||||
$('#modalAlert div.modal-body').html("There was an error parsing the public key from the JSON Web Key set. Check the value and try again.");
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
|
||||
return false;
|
||||
app.errorHandlerView.showErrorMessage($.t("client.client-form.error.jwk-set"), $.t("client.client-form.error.jwk-set-parse"));
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
jwksUri = null;
|
||||
|
@ -986,6 +936,7 @@ var ClientFormView = Backbone.View.extend({
|
|||
jwksUri: jwksUri,
|
||||
jwks: jwks,
|
||||
subjectType: subjectType,
|
||||
softwareStatement: $('#softwareStatement textarea').val(),
|
||||
tokenEndpointAuthMethod: tokenEndpointAuthMethod,
|
||||
responseTypes: responseTypes,
|
||||
sectorIdentifierUri: sectorIdentifierUri,
|
||||
|
@ -1041,6 +992,8 @@ var ClientFormView = Backbone.View.extend({
|
|||
secretChanged: secretChanged
|
||||
};
|
||||
|
||||
$('#modalAlert div.modal-header').html($.t('client.client-form.saved.saved'));
|
||||
|
||||
$('#modalAlert .modal-body').html(_self.clientSavedTemplate(savedModel));
|
||||
|
||||
$('#modalAlert .modal-body #savedClientSecret').hide();
|
||||
|
@ -1061,22 +1014,7 @@ var ClientFormView = Backbone.View.extend({
|
|||
app.clientList.add(_self.model);
|
||||
app.navigate('admin/clients', {trigger:true});
|
||||
},
|
||||
error:function (error, response) {
|
||||
console.log("An error occurred when saving a client");
|
||||
|
||||
//Pull out the response text.
|
||||
var responseJson = JSON.parse(response.responseText);
|
||||
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html(responseJson.error);
|
||||
$('#modalAlert div.modal-body').html(responseJson.error_description);
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
}
|
||||
error:app.errorHandlerView.handleError({log: "An error occurred when saving a client"})
|
||||
});
|
||||
|
||||
return false;
|
||||
|
|
|
@ -102,7 +102,7 @@ var DynRegRootView = Backbone.View.extend({
|
|||
$('#loadingbox').sheet('show');
|
||||
$('#loading').html('<span class="label" id="loading-scopes">' + $.t('common.scopes') + '</span> ');
|
||||
|
||||
$.when(this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}}))
|
||||
$.when(this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}, error:app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
$('#loadingbox').sheet('hide');
|
||||
callback();
|
||||
|
@ -133,46 +133,39 @@ var DynRegRootView = Backbone.View.extend({
|
|||
|
||||
var self = this;
|
||||
|
||||
client.fetch({success: function() {
|
||||
client.fetch({
|
||||
success: function() {
|
||||
|
||||
var userInfo = getUserInfo();
|
||||
var contacts = client.get("contacts");
|
||||
if (userInfo != null && userInfo.email != null && ! _.contains(contacts, userInfo.email)) {
|
||||
contacts.push(userInfo.email);
|
||||
}
|
||||
client.set({
|
||||
contacts: contacts
|
||||
}, { silent: true });
|
||||
|
||||
if (client.get("jwks")) {
|
||||
client.set({
|
||||
jwksType: "VAL"
|
||||
}, { silent: true });
|
||||
} else {
|
||||
client.set({
|
||||
jwksType: "URI"
|
||||
}, { silent: true });
|
||||
}
|
||||
|
||||
var view = new DynRegEditView({model: client, systemScopeList: app.systemScopeList});
|
||||
|
||||
view.load(function() {
|
||||
$('#content').html(view.render().el);
|
||||
view.delegateEvents();
|
||||
setPageTitle($.t('dynreg.edit-dynamically-registered'));
|
||||
app.navigate('dev/dynreg/edit', {trigger: true});
|
||||
self.remove();
|
||||
});
|
||||
}, error: function() {
|
||||
$('#modalAlert div.modal-body').html($.t('dynreg.invalid-access-token'));
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
|
||||
}});
|
||||
var userInfo = getUserInfo();
|
||||
var contacts = client.get("contacts");
|
||||
if (userInfo != null && userInfo.email != null && ! _.contains(contacts, userInfo.email)) {
|
||||
contacts.push(userInfo.email);
|
||||
}
|
||||
client.set({
|
||||
contacts: contacts
|
||||
}, { silent: true });
|
||||
|
||||
if (client.get("jwks")) {
|
||||
client.set({
|
||||
jwksType: "VAL"
|
||||
}, { silent: true });
|
||||
} else {
|
||||
client.set({
|
||||
jwksType: "URI"
|
||||
}, { silent: true });
|
||||
}
|
||||
|
||||
var view = new DynRegEditView({model: client, systemScopeList: app.systemScopeList});
|
||||
|
||||
view.load(function() {
|
||||
$('#content').html(view.render().el);
|
||||
view.delegateEvents();
|
||||
setPageTitle($.t('dynreg.edit-dynamically-registered'));
|
||||
app.navigate('dev/dynreg/edit', {trigger: true});
|
||||
self.remove();
|
||||
});
|
||||
}, error:app.errorHandlerView.handleError({message: $.t('dynreg.invalid-access-token')})
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -207,7 +200,7 @@ var DynRegEditView = Backbone.View.extend({
|
|||
$('#loadingbox').sheet('show');
|
||||
$('#loading').html('<span class="label" id="loading-scopes">' + $.t('common.scopes') + '</span> ');
|
||||
|
||||
$.when(this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}}))
|
||||
$.when(this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}, error:app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
$('#loadingbox').sheet('hide');
|
||||
callback();
|
||||
|
@ -240,22 +233,7 @@ var DynRegEditView = Backbone.View.extend({
|
|||
self.remove();
|
||||
app.navigate('dev/dynreg', {trigger: true});
|
||||
},
|
||||
error:function (error, response) {
|
||||
console.log("An error occurred when deleting a client");
|
||||
|
||||
//Pull out the response text.
|
||||
var responseJson = JSON.parse(response.responseText);
|
||||
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html(responseJson.error);
|
||||
$('#modalAlert div.modal-body').html(responseJson.error_description);
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
}
|
||||
error:app.errorHandlerView.handleError({"log": "An error occurred when deleting a client"})
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -397,16 +375,8 @@ var DynRegEditView = Backbone.View.extend({
|
|||
var sectorIdentifierUri = $('#sectorIdentifierUri input').val();
|
||||
if (subjectType == 'PAIRWISE' && redirectUris.length > 1 && sectorIdentifierUri == '') {
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html("Consistency error");
|
||||
$('#modalAlert div.modal-body').html("Pairwise identifiers cannot be used with multiple redirect URIs unless a sector identifier URI is also registered.");
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
|
||||
return false;
|
||||
app.errorHandlerView.showErrorMessage($.t("client.client-form.error.consistency"), $.t("client.client-form.error.pairwise-sector"));
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
|
@ -424,18 +394,8 @@ var DynRegEditView = Backbone.View.extend({
|
|||
jwks = JSON.parse($('#jwks textarea').val());
|
||||
} catch (e) {
|
||||
console.log("An error occurred when parsing the JWK Set");
|
||||
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html("JWK Set Error");
|
||||
$('#modalAlert div.modal-body').html("There was an error parsing the public key from the JSON Web Key set. Check the value and try again.");
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
|
||||
return false;
|
||||
app.errorHandlerView.showErrorMessage($.t("client.client-form.error.jwk-set"), $.t("client.client-form.error.jwk-set-parse"));
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
jwksUri = null;
|
||||
|
@ -456,6 +416,7 @@ var DynRegEditView = Backbone.View.extend({
|
|||
jwks_uri: jwksUri,
|
||||
jwks: jwks,
|
||||
subject_type: subjectType,
|
||||
software_statement: $('#softwareStatement textarea').val(),
|
||||
token_endpoint_auth_method: $('#tokenEndpointAuthMethod input').filter(':checked').val(),
|
||||
response_types: responseTypes,
|
||||
sector_identifier_uri: sectorIdentifierUri,
|
||||
|
@ -518,22 +479,7 @@ var DynRegEditView = Backbone.View.extend({
|
|||
view.delegateEvents();
|
||||
});
|
||||
},
|
||||
error:function (error, response) {
|
||||
console.log("An error occurred when saving the client");
|
||||
|
||||
//Pull out the response text.
|
||||
var responseJson = JSON.parse(response.responseText);
|
||||
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html(responseJson.error);
|
||||
$('#modalAlert div.modal-body').html(responseJson.error_description);
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
}
|
||||
error:app.errorHandlerView.handleError({log: "An error occurred when saving a client"})
|
||||
});
|
||||
|
||||
return false;
|
||||
|
|
|
@ -53,9 +53,9 @@ var ApprovedSiteListView = Backbone.View.extend({
|
|||
'<span class="label" id="loading-scopes">' + $.t('common.scopes') + '</span> '
|
||||
);
|
||||
|
||||
$.when(this.model.fetchIfNeeded({success:function(e) {$('#loading-grants').addClass('label-success');}}),
|
||||
this.options.clientList.fetchIfNeeded({success:function(e) {$('#loading-clients').addClass('label-success');}}),
|
||||
this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}}))
|
||||
$.when(this.model.fetchIfNeeded({success:function(e) {$('#loading-grants').addClass('label-success');}, error:app.errorHandlerView.handleError()}),
|
||||
this.options.clientList.fetchIfNeeded({success:function(e) {$('#loading-clients').addClass('label-success');}, error:app.errorHandlerView.handleError()}),
|
||||
this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}, error:app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
$('#loadingbox').sheet('hide');
|
||||
callback();
|
||||
|
@ -115,9 +115,9 @@ var ApprovedSiteListView = Backbone.View.extend({
|
|||
'<span class="label" id="loading-scopes">' + $.t('common.scopes') + '</span> '
|
||||
);
|
||||
|
||||
$.when(this.model.fetch({success:function(e) {$('#loading-grants').addClass('label-success');}}),
|
||||
this.options.clientList.fetch({success:function(e) {$('#loading-clients').addClass('label-success');}}),
|
||||
this.options.systemScopeList.fetch({success:function(e) {$('#loading-scopes').addClass('label-success');}}))
|
||||
$.when(this.model.fetch({success:function(e) {$('#loading-grants').addClass('label-success');}, error:app.errorHandlerView.handleError()}),
|
||||
this.options.clientList.fetch({success:function(e) {$('#loading-clients').addClass('label-success');}, error:app.errorHandlerView.handleError()}),
|
||||
this.options.systemScopeList.fetch({success:function(e) {$('#loading-scopes').addClass('label-success');}, error:app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
$('#loadingbox').sheet('hide');
|
||||
_self.render();
|
||||
|
@ -227,22 +227,7 @@ var ApprovedSiteView = Backbone.View.extend({
|
|||
});
|
||||
});
|
||||
},
|
||||
error:function (error, response) {
|
||||
|
||||
//Pull out the response text.
|
||||
var responseJson = JSON.parse(response.responseText);
|
||||
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html(responseJson.error);
|
||||
$('#modalAlert div.modal-body').html(responseJson.error_description);
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
}
|
||||
|
||||
error:app.errorHandlerView.handleError()
|
||||
});
|
||||
|
||||
this.parentView.delegateEvents();
|
||||
|
|
|
@ -158,6 +158,9 @@
|
|||
"ps384": "RSASSA-PSS using SHA-384 and MGF1 with SHA-384",
|
||||
"ps512": "RSASSA-PSS using SHA-512 and MGF1 with SHA-512"
|
||||
},
|
||||
"software-statement": "Software Statement",
|
||||
"software-statement-placeholder": "eyj0...",
|
||||
"software-statement-help": "A software statement is issued by a trusted third party and locks certain elements of a client's registration",
|
||||
"subject-type": "Subject Type",
|
||||
"terms": "Terms of Service",
|
||||
"terms-help": "URL for the Terms of Service of this client, will be displayed to the user",
|
||||
|
@ -169,7 +172,13 @@
|
|||
"unknown": "(Unknown)",
|
||||
"user-info-crypto-algorithm": "User Info Endpoint Encryption Algorithm",
|
||||
"user-info-crypto-method": "User Info Endpoint Encryption Method",
|
||||
"user-info-signing-algorithm": "User Info Endpoint Signing Algorithm"
|
||||
"user-info-signing-algorithm": "User Info Endpoint Signing Algorithm",
|
||||
"error": {
|
||||
"consistency": "Consistency error",
|
||||
"pairwise-sector": "Pairwise identifiers cannot be used with multiple redirect URIs unless a sector identifier URI is also registered.",
|
||||
"jwk-set": "JWK set error",
|
||||
"jwk-set-parse": "There was an error parsing the public key from the JSON Web Key set. Check the value and try again."
|
||||
}
|
||||
},
|
||||
"client-table": {
|
||||
"allow-introspection-tooltip": "This client can perform token introspection",
|
||||
|
@ -348,6 +357,13 @@
|
|||
"whitelist-table": {
|
||||
"no-sites": "There are no whitelisted sites. Use the <strong>whitelist</strong> button on the client management page to create one."
|
||||
}
|
||||
},
|
||||
"blacklist": {
|
||||
"text": "Blacklisted URIs cannot be used as redirect URIs by any registered clients, whether in the admin interface or in dynamic registration.",
|
||||
"blacklist-uri-placeholder": "blacklist uri",
|
||||
"add": "Add URI to blacklist",
|
||||
"empty": "There are no blacklisted URIs on this server.",
|
||||
"uri": "URI"
|
||||
},
|
||||
"copyright": "Powered by <a href=\"https://github.com/mitreid-connect/\">MITREid Connect <span class=\"label\">{0}</span></a> <span class=\"pull-right\">© 2016 The MITRE Corporation and MIT Internet Trust Consortium.</span>.",
|
||||
"about": {
|
||||
|
@ -461,11 +477,18 @@
|
|||
},
|
||||
"error": {
|
||||
"title": "Error",
|
||||
"header": "Error:",
|
||||
"message": "There was an error processing your request. The server''s message was:"
|
||||
"header": "Error",
|
||||
"header-with-message": "Error: ",
|
||||
"reload": "Additionally, it looks like you're not logged in. Reload the page to try again. You will likely lose any unsaved work.",
|
||||
"reload-button": "Reload",
|
||||
"message": "There was an error processing your request.",
|
||||
"server-message": "The server said: "
|
||||
},
|
||||
"login": {
|
||||
"login_with_username_and_password": "Login with Username and Password",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"login-button": "Login",
|
||||
"error": "The system was unable to log you in. Please try again."
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,490 @@
|
|||
{
|
||||
"admin": {
|
||||
"blacklist": "黑名单",
|
||||
"blacklist-form": {
|
||||
"blacklisted-uris": "列入黑名单的URI"
|
||||
},
|
||||
"home": "首页",
|
||||
"list-widget": {
|
||||
"empty": "此列表为空。",
|
||||
"tooltip": "单击显示全部值。"
|
||||
},
|
||||
"manage-blacklist": "管理列入黑名单的客户端",
|
||||
"self-service-client": "自助服务-客户端注册",
|
||||
"self-service-resource": "自助服务-受保护资源注册",
|
||||
"user-profile": {
|
||||
"claim": "声明项",
|
||||
"show": "查看用户信息",
|
||||
"text": "您的用户信息如下:",
|
||||
"value": "内容"
|
||||
}
|
||||
},
|
||||
"client": {
|
||||
"client-form": {
|
||||
"access": "访问",
|
||||
"access-token-no-timeout": "访问令牌不时间",
|
||||
"access-token-timeout": "访问令牌超时",
|
||||
"access-token-timeout-help": "输入时间(秒、分钟或小时)。",
|
||||
"acr-values": "默认ACR值",
|
||||
"acr-values-placeholder": "新的ACR值",
|
||||
"acr-values-help": "用于请求该客户端的默认身份验证上下文参考",
|
||||
"allow-introspection": "允许调用内省端点?",
|
||||
"authentication-method": "令牌端点认证方法",
|
||||
"authorization-code": "授权码",
|
||||
"client-credentials": "客户端凭证",
|
||||
"client-description": "描述",
|
||||
"client-description-help": "人类可读的文本描述",
|
||||
"client-description-placeholder": "填入说明描述",
|
||||
"client-id": "客户端ID",
|
||||
"client-id-help": "唯一标识符。如果不填则系统会自动生成一个。",
|
||||
"client-id-placeholder": "输入一些字符",
|
||||
"client-name": "客户端名称",
|
||||
"client-name-help": "人类可读的应用程序名称",
|
||||
"client-name-placeholder": "输入一些字符",
|
||||
"client-secret": "客户端密钥",
|
||||
"client-secret-placeholder": "输入密钥",
|
||||
"contacts": "联系人",
|
||||
"contacts-help": "此客户端管理员的联系人名单。",
|
||||
"contacts-placeholder": "新联系人",
|
||||
"credentials": "凭据",
|
||||
"crypto": {
|
||||
"a128cbc-hs256": "复合认证加密算法,采用密码块链(CBC)模式AES,以PKCS #5填充,完整性计算使用HMAC SHA-256,并使用256位的CMK(和128位CEK)",
|
||||
"a256cbc-hs512": "复合认证加密算法,采用密码块链(CBC)模式AES,以PKCS #5填充,完整性计算使用HMAC SHA-512,并使用512位的CMK(和256位CEK)",
|
||||
"a128gcm": "AES GCM使用128位的密钥",
|
||||
"a256gcm": "AES GCM使用256位的密钥",
|
||||
"a128kw": "AES密钥封装算法使用128位的密钥",
|
||||
"a256kw": "AES密钥封装算法使用256位的密钥",
|
||||
"default": "使用服务器默认",
|
||||
"dir": "直接使用一个共享对称密钥作为块加密的内容主密钥(CMK)",
|
||||
"ecdh-es": "椭圆曲线Diffie-Hellman短时静态密钥协议(使用Concat KDF),商定的密钥被直接用作内容主密钥(CMK)",
|
||||
"ecdh-es-a128kw": "椭圆曲线Diffie-Hellman短时静态密钥协议(使用ECDH-ES和第4.7小节),但商定的密钥是用以A128KW函数封装内容主密钥(CMK)",
|
||||
"ecdh-es-a256kw": "椭圆曲线Diffie-Hellman短时静态密钥协议(使用ECDH-ES和第4.7小节),但商定的密钥是用以A256KW函数封装内容主密钥(CMK)",
|
||||
"none": "不加密",
|
||||
"rsa-oaep": "RSAES使用最优不对称加密填充(OAEP)",
|
||||
"rsa1-5": "RSAES-PKCS1-V1_5"
|
||||
},
|
||||
"cryptography": "密码",
|
||||
"display-secret": "显示/编辑客户端密钥:",
|
||||
"edit": "编辑客户端",
|
||||
"generate-new-secret": "生成一个新的客户端密钥吗?",
|
||||
"generate-new-secret-help": "当点击“保存”时生成新的密钥",
|
||||
"generate-on-save": "保存时生成",
|
||||
"grant-types": "批准的类型",
|
||||
"home": "主页",
|
||||
"home-help": "客户端首页的URL,将显示给用户",
|
||||
"hours": "小时",
|
||||
"id": "ID:",
|
||||
"id-token-crypto-algorithm": "身份令牌加密算法",
|
||||
"id-token-crypto-method": "身份令牌加密方法",
|
||||
"id-token-signing-algorithm": "身份令牌签名算法",
|
||||
"id-token-timeout": "身份令牌超时",
|
||||
"implicit": "隐式的",
|
||||
"initiate-login": "初始化登录",
|
||||
"initiate-login-help": "启动登录客户端的URL",
|
||||
"introspection": "自省",
|
||||
"jwk-set": "公钥集",
|
||||
"jwk-set-help": "客户端JSON Web Key集的URL (须可被服务器访问)",
|
||||
"jwk-set-value-help": "客户端JSON Web Key集的URL (须可被服务器访问)",
|
||||
"logo": "标志(Logo)",
|
||||
"logo-help": "标志(Logo)图像的URL,将显示在批准页",
|
||||
"main": "首要",
|
||||
"max-age": "默认最长有效时间",
|
||||
"max-age-help": "再提示之前的默认最长会话有效时间",
|
||||
"minutes": "分钟",
|
||||
"new": "新客户端",
|
||||
"other": "其它",
|
||||
"pairwise": "Pairwise对",
|
||||
"password": "密码",
|
||||
"policy": "政策声明",
|
||||
"policy-help": "此客户端的政策声明链接,将显示给用户",
|
||||
"post-logout": "注销后重定向",
|
||||
"post-logout-help": "客户端注销操作后的重定向URL",
|
||||
"public": "公共",
|
||||
"redelegation": "重新授权",
|
||||
"redirect-uris": "重定向URI",
|
||||
"redirect-uris-help": "在授权页面之后客户端重定向URI",
|
||||
"claims-redirect-uris": "声明重定向URI",
|
||||
"claims-redirect-uris-help": "在声明收集步骤之后浏览器跳转至的目的地址",
|
||||
"refresh": "刷新",
|
||||
"refresh-tokens": "刷新令牌",
|
||||
"refresh-tokens-issued": "为此客户端发布的刷新令牌",
|
||||
"refresh-tokens-issued-help": "这将把 offline_access 加入客户端的范围。",
|
||||
"refresh-tokens-reused": "此客户端的刷新令牌被重用",
|
||||
"clear-access-tokens": "当刷新令牌用过之后,已激活的访问令牌自动失效",
|
||||
"refresh-tokens-no-expire": "刷新令牌尚未过期",
|
||||
"registered": "注册于",
|
||||
"registration-token": "注册令牌:",
|
||||
"registration-access-token": "注册访问令牌",
|
||||
"registration-token-error": "无法为此客户端下载注册访问令牌。",
|
||||
"request-object-signing-algorithm": "请求对象签名算法",
|
||||
"request-uri": "请求的URI",
|
||||
"request-uri-help": "URI包含此客户端使用的请求对象",
|
||||
"require-auth-time": "需要身份认证时间(auth_time)",
|
||||
"require-auth-time-label": "总是需要在身份令牌中包含auth_time声明",
|
||||
"response-types": "响应类型",
|
||||
"rotate-registration-token": "旋转注册令牌",
|
||||
"rotate-registration-token-confirm": "你确定你想旋转这个客户端的登录令牌?",
|
||||
"rotate-registration-token-error": "无法旋转该客户端的注册访问令牌。",
|
||||
"saved": {
|
||||
"no-secret": "没有客户端密钥",
|
||||
"saved": "客户端已保存",
|
||||
"secret": "密钥:",
|
||||
"show-secret": "显示密钥",
|
||||
"unchanged": "不变"
|
||||
},
|
||||
"scope-placeholder": "新范围",
|
||||
"scope-help": "OAuth范围允许客户端请求",
|
||||
"seconds": "秒",
|
||||
"secret-asymmetric-jwt": "非对称签名JWT断言",
|
||||
"secret-http": "客户端密钥经由HTTP Basic",
|
||||
"secret-none": "没有认证",
|
||||
"secret-post": "客户端密钥经由HTTP POST",
|
||||
"secret-symmetric-jwt": "客户端密钥经由对称签名JWT断言",
|
||||
"sector-identifier": "扇区标识符URI",
|
||||
"signing": {
|
||||
"any": "允许",
|
||||
"default": "使用服务器默认",
|
||||
"es256": "ECDSA采用P-256曲线和SHA-256哈希算法",
|
||||
"es384": "ECDSA采用P-384曲线及SHA-384哈希算法",
|
||||
"es512": "ECDSA采用P-512曲线及SHA-512哈希算法",
|
||||
"hs256": "HMAC使用SHA-256哈希算法",
|
||||
"hs384": "HMAC使用SHA-384哈希算法",
|
||||
"hs512": "HMAC使用SHA-512哈希算法",
|
||||
"none": "没有数字签名",
|
||||
"rs256": "RSASSA使用SHA-256哈希算法",
|
||||
"rs384": "RSASSA采用SHA-384哈希算法",
|
||||
"rs512": "RSASSA使用SHA-512哈希算法",
|
||||
"ps256": "采用SHA-256和MGF1的RSASSA-PSS算法",
|
||||
"ps384": "采用SHA-384和MGF1的RSASSA-PSS算法",
|
||||
"ps512": "采用SHA-512和MGF1的RSASSA-PSS算法"
|
||||
},
|
||||
"subject-type": "主体类型",
|
||||
"terms": "服务条款",
|
||||
"terms-help": "此客户服务条款的URL,将向用户显示",
|
||||
"token-signing-algorithm": "令牌端点认证签名算法",
|
||||
"tokens": "令牌",
|
||||
"type": "应用类型",
|
||||
"type-native": "原生应用",
|
||||
"type-web": "网络应用",
|
||||
"unknown": "(未知)",
|
||||
"user-info-crypto-algorithm": "用户信息端点加密算法",
|
||||
"user-info-crypto-method": "用户信息端点加密方法",
|
||||
"user-info-signing-algorithm": "用户信息端点签名算法"
|
||||
},
|
||||
"client-table": {
|
||||
"allow-introspection-tooltip": "这个客户端可以执行令牌自省",
|
||||
"confirm": "你确定要删除这个客户端?",
|
||||
"dynamically-registered-tooltip": "这个客户端是动态注册的。点击查看注册访问令牌",
|
||||
"match": {
|
||||
"contacts": "联系人",
|
||||
"description": "描述",
|
||||
"homepage": "主页",
|
||||
"id": "身份",
|
||||
"logo": "标志",
|
||||
"name": "名称",
|
||||
"policy": "政策",
|
||||
"redirect": "重定向URI",
|
||||
"scope": "范围",
|
||||
"terms": "服务条款"
|
||||
},
|
||||
"matched-search": "匹配搜索:",
|
||||
"new": "新客户端",
|
||||
"no-clients": "此服务器上没有注册的客户端。",
|
||||
"no-matches": "没有匹配搜索条件的客户端。",
|
||||
"no-redirect": "没有重定向URI",
|
||||
"registered": "注册于",
|
||||
"search": "搜索……",
|
||||
"whitelist": "白名单",
|
||||
"unknown": "一个未知的时间"
|
||||
},
|
||||
"manage": "管理客户端",
|
||||
"more-info": {
|
||||
"contacts": "管理员联系方式:",
|
||||
"home": "主页",
|
||||
"more": "更多信息",
|
||||
"policy": "政策",
|
||||
"terms": "服务条款:"
|
||||
},
|
||||
"newClient": "新客户端"
|
||||
},
|
||||
"common": {
|
||||
"cancel": "取消",
|
||||
"client": "客户端",
|
||||
"clients": "客户端",
|
||||
"close": "关闭",
|
||||
"delete": "删除",
|
||||
"description": "描述",
|
||||
"dynamically-registered": "这个客户端是动态注册的",
|
||||
"edit": "编辑",
|
||||
"expires": "到期:",
|
||||
"information": "信息",
|
||||
"new": "新建",
|
||||
"not-yet-implemented": "未实现",
|
||||
"not-yet-implemented-content": "这个字段的值将于客户端保存,但服务器目前不处理任何事情。服务器的未来库版本将利用它。",
|
||||
"revoke": "撤销",
|
||||
"save": "保存",
|
||||
"scopes": "范围",
|
||||
"statistics": "统计",
|
||||
"refresh": "刷新",
|
||||
"scope": "范围",
|
||||
"users": "用户",
|
||||
"user": "用户",
|
||||
"roles": "角色",
|
||||
"role": "角色",
|
||||
"email": "电子邮箱",
|
||||
"active": "已激活",
|
||||
"inactive": "未激活"
|
||||
},
|
||||
"dynreg": {
|
||||
"client-id-placeholder": "输入客户端ID",
|
||||
"configuration-url": "客户端配置URL",
|
||||
"edit-dynamically-registered": "编辑动态注册的客户端",
|
||||
"edit-existing": "编辑一个现有的客户端",
|
||||
"edit-existing-help": "用于编辑之前已注册的客户端。粘贴您的客户端ID和注册访问令牌,以便访问该客户端。",
|
||||
"edit-existing-button": "编辑客户端",
|
||||
"invalid-access-token": "无效的客户端或注册访问令牌。",
|
||||
"new-client": "注册新客户端",
|
||||
"new-client-help": "用于注册新的客户端。请提供客户端ID和注册访问令牌,以便管理您的客户端。",
|
||||
"new-client-button": "新建客户端",
|
||||
"regtoken-placeholder": "输入注册访问令牌",
|
||||
"warning": "<strong>警告!</strong>你必须保护好<b>客户端ID </b>,<b>客户密钥(如果提供)</b>,以及您的<b>注册访问令牌</b>。如果你丢失了客户端ID或注册访问令牌,将无法访问您的客户端注册记录,你需要注册一个新客户端。",
|
||||
"will-be-generated": "当保存客户端信息将由服务器生成"
|
||||
},
|
||||
"grant": {
|
||||
"manage-approved-sites": "管理批准的网站",
|
||||
"refresh": "刷新",
|
||||
"grant-table": {
|
||||
"active-tokens": "当前活跃的访问令牌数量",
|
||||
"application": "应用程序",
|
||||
"approved-sites": "许可站点",
|
||||
"authorized": "授权:",
|
||||
"dynamically-registered": "这个客户端是动态注册的",
|
||||
"expires": "到期:",
|
||||
"last-accessed": "上次访问:",
|
||||
"never": "从未",
|
||||
"no-sites": "还未批准任何网站。",
|
||||
"no-whitelisted": "还未访问任何白名单的网站。",
|
||||
"pre-approved": "这些都是预先由管理员批准的网站。",
|
||||
"text": "这些都是您已经手动批准的网站。如果同一网站将来要进行同样的访问,它将直接通过、且没有提示。",
|
||||
"unknown": "未知",
|
||||
"whitelist-note": "<b>注:</b>如果你在此撤销它们,它们将在您下次访问时不经提示即被自动重新批准。",
|
||||
"whitelisted-site": "这个网站由管理员列入白名单中",
|
||||
"whitelisted-sites": "白名单的网站"
|
||||
}
|
||||
},
|
||||
"rsreg": {
|
||||
"resource-id-placeholder": "输入资源ID",
|
||||
"configuration-url": "客户端配置URL",
|
||||
"edit": "编辑受保护的资源",
|
||||
"edit-existing": "编辑现有的保护资源",
|
||||
"edit-existing-help": "用于编辑之前已注册的资源。请提供您的客戶端ID和注册访问令牌来访问资源的属性。",
|
||||
"edit-existing-button": "编辑资源",
|
||||
"invalid-access-token": "无效的客户端或注册访问令牌。",
|
||||
"new-client": "注册新的受保护资源",
|
||||
"new-client-help": "用于注册新的资源。请提供客户端ID和注册访问令牌,以便管理您的资源。",
|
||||
"new-client-button": "新建资源",
|
||||
"regtoken-placeholder": "输入注册访问令牌",
|
||||
"will-be-generated": "当保存资源信息将由服务器生成",
|
||||
"warning": "<strong>警告!</strong>你必须保护好<b>客户端ID </b>,<b>客户密钥(如果提供)</b>,以及<b>注册访问令牌</b>。如果丢失了客户端ID或注册访问令牌,将无法再次获得您的客户端注册记录,你需要注册一个新客户端。",
|
||||
"client-form": {
|
||||
"scope-help": "这个资源能够自省令牌的范围。"
|
||||
}
|
||||
},
|
||||
"scope": {
|
||||
"manage": "管理系统范围",
|
||||
"scope-list": {
|
||||
"no-scopes": "没有范围"
|
||||
},
|
||||
"system-scope-form": {
|
||||
"default": "默认范围",
|
||||
"default-help": "新创建的用户默认情况下获得这个范围?",
|
||||
"description-help": "人类可读的文本描述",
|
||||
"description-placeholder": "输入说明",
|
||||
"restricted": "限制",
|
||||
"restricted-help": "限制范围只能由系统管理员使用,可用动态注册客户和保护资源",
|
||||
"edit": "编辑范围",
|
||||
"icon": "图标",
|
||||
"new": "新范围",
|
||||
"select-icon": "选择图标",
|
||||
"structured": "是一个结构化的范围",
|
||||
"structured-help": "范围结构化是否包含如<code>base:extension</code>的结构化值?",
|
||||
"structured-param-help": "人类可读的结构化参数描述",
|
||||
"subject-type": "主体类型",
|
||||
"value": "范围值",
|
||||
"value-help": "不含空格的单个字符串",
|
||||
"value-placeholder": "范围"
|
||||
},
|
||||
"system-scope-table": {
|
||||
"confirm": "你确定要删除此范围?引用了此范围的客户端还需要它。",
|
||||
"new": "新范围",
|
||||
"text": "尚未定义系统范围。客户可自定义范围。",
|
||||
"tooltip-restricted": "此范围只能由管理员使用。它不能用于动态注册。",
|
||||
"tooltip-default": "这个范围将自动分配给新注册的客户。"
|
||||
}
|
||||
},
|
||||
"token": {
|
||||
"manage": "管理活动的令牌",
|
||||
"token-table": {
|
||||
"access-tokens": "访问令牌",
|
||||
"associated-id": "这个访问令牌附带相关的身份令牌。",
|
||||
"associated-refresh": "这个访问令牌附带相关的刷新令牌。",
|
||||
"click-to-display": "点击显示完整的令牌值",
|
||||
"confirm": "你确定要撤销这个令牌?",
|
||||
"confirm-refresh": "你确定要撤销这个刷新令牌及其相关的访问令牌?",
|
||||
"expires": "过期",
|
||||
"no-access": "没有活动的访问令牌。",
|
||||
"no-refresh": "没有活动的刷新令牌。",
|
||||
"number-of-tokens": "关联的访问令牌数量",
|
||||
"refresh-tokens": "刷新令牌",
|
||||
"text": "访问令牌通常是短暂的,供客户端访问特定的资源。身份令牌是采用OpenID Connect协议登录的、专门的访问令牌。",
|
||||
"text-refresh": "刷新令牌通常是长期的,以便客户端能无需用户介入即可获取新的访问令牌。",
|
||||
"token-info": "令牌的信息"
|
||||
}
|
||||
},
|
||||
"whitelist": {
|
||||
"confirm": "你确定要删除这个白名单项?",
|
||||
"edit": "编辑白名单",
|
||||
"manage": "管理列入白名单的网站",
|
||||
"new": "新白名单",
|
||||
"whitelist": "白名单",
|
||||
"whitelist-form": {
|
||||
"allowed-scopes": "允许范围",
|
||||
"edit": "编辑白名单的网站",
|
||||
"new": "新增白名单网站",
|
||||
"scope-help": "当客户端发出请求列表时将自动批准的范围",
|
||||
"scope-placeholder": "新范围"
|
||||
},
|
||||
"whitelist-table": {
|
||||
"no-sites": "白名单列表为空。使用<strong>白名单</strong>按钮在客户端管理页面创建一个。"
|
||||
}
|
||||
},
|
||||
"blacklist": {
|
||||
"text": "被拉黑的网站URI将无法用做注册客户端的重定向地址(无论是在管理界面中添加、还是动态注册,都不会成功)。",
|
||||
"blacklist-uri-placeholder": "要拉黑的网站URI",
|
||||
"add": "将网站URI加入黑名单",
|
||||
"empty": "当前黑名单为空",
|
||||
"uri": "URI"
|
||||
},
|
||||
"copyright": "基于<a href=\"https://github.com/mitreid-connect/\">MITREid Connect <span class=\"label\">{0}</span></a>技术构建 <span class=\"pull-right\">© 2016 MITRE公司及MIT因特网信任联盟</span>.",
|
||||
"about": {
|
||||
"title": "关于",
|
||||
"body": "\n此OpenID Connect服务基于开源的MITREid Connect项目,该项目来自 \n<a href=\"http://www.mitre.org/\">MITRE公司</a> 及 <a href=\"http://kit.mit.edu/\">MIT因特网信任联盟</a>。\n</p>\n<p>\n有关项目的更多信息可见 \n<a href=\"http://github.com/mitreid-connect/\">GitHub上的MITREid Connect项目</a>。 \n在那儿,您可以提交bug报告、提交反馈甚或提交代码补丁。"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "统计",
|
||||
"number_users": "用户数: <span class=\"label label-info\" id=\"userCount\">{0}</span>",
|
||||
"number_clients": "授权的客户端: <span class=\"label label-info\" id=\"clientCount\">{0}</span>",
|
||||
"number_approvals": "已批准的站点: <span class=\"label label-info\" id=\"approvalCount\">{0}</span>"
|
||||
},
|
||||
"home": {
|
||||
"title": "首页",
|
||||
"welcome": {
|
||||
"title": "欢迎!",
|
||||
"body": "\nOpenID Connect是适于因特网部署的身份联邦认证服务器,基于OAuth2授权框架之上的OpenID Connect技术构建。\nOpenID Connect让您无需暴露自己的用户名、密码即可便捷登录网站。</p>\n<p><a class=\"btn btn-primary btn-large\" href=\"http://openid.net/connect/\">了解更多信息»</a>"
|
||||
},
|
||||
"more": "更多",
|
||||
"about": {
|
||||
"title": "关于",
|
||||
"body": "本服务基于开源的MITREid Connect项目,该项目来自 \n<a href=\"http://www.mitre.org/\">MITRE公司</a> 及 <a href=\"http://kit.mit.edu/\">MIT因特网信任联盟</a>。"
|
||||
},
|
||||
"contact": {
|
||||
"title": "联系方式",
|
||||
"body": "\n如需更多的信息和支持,请联系本系统的管理员。</p>\n<p><a class=\"btn\" href=\"mailto:idp@example.com?Subject=OpenID Connect\">电子信箱 »</a>"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "当前统计",
|
||||
"loading": "加载……",
|
||||
"number_users": "用户数: <span class=\"label label-info\" id=\"userCount\">{0}</span>",
|
||||
"number_clients": "授权的客户端: <span class=\"label label-info\" id=\"clientCount\">{0}</span>",
|
||||
"number_approvals": "已批准的站点: <span class=\"label label-info\" id=\"approvalCount\">{0}</span>"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"title": "联系方式",
|
||||
"body": "如果要报告有关MITREid Connect软件自身的bug,请访问\n<a href=\"https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/issues\">GitHub issue追踪系统</a>。 \n有关当前服务器的问题,请联系服务器管理员。"
|
||||
},
|
||||
"topbar": {
|
||||
"about": "关于",
|
||||
"contact": "联系方式",
|
||||
"statistics": "统计",
|
||||
"home": "首页",
|
||||
"login": "登录",
|
||||
"logout": "注销"
|
||||
},
|
||||
"sidebar": {
|
||||
"administrative": {
|
||||
"title": "管理",
|
||||
"manage_clients": "管理客户端",
|
||||
"whitelisted_clients": "白名单",
|
||||
"blacklisted_clients": "黑名单",
|
||||
"system_scopes": "系统范围"
|
||||
},
|
||||
"personal": {
|
||||
"title": "个人",
|
||||
"approved_sites": "管理批准的网站",
|
||||
"active_tokens": "管理活动的令牌",
|
||||
"profile_information": "查看用户信息"
|
||||
},
|
||||
"developer": {
|
||||
"title": "开发者自助服务",
|
||||
"client_registration": "客户端注册",
|
||||
"resource_registration": "保护资源注册"
|
||||
}
|
||||
},
|
||||
"manage": {
|
||||
"ok": "好的",
|
||||
"loading": "加载",
|
||||
"title": "管理控制台"
|
||||
},
|
||||
"approve": {
|
||||
"dynamically-registered-unknown": "在一个未知的时间",
|
||||
"title": "批准访问",
|
||||
"error": {
|
||||
"not_granted": "访问可能不获批准。"
|
||||
},
|
||||
"required_for": "有待批准",
|
||||
"dynamically_registered": "此客户端已被动态注册了<span class=\"label label-info\" id=\"registrationTime\">{0}</span>次。",
|
||||
"caution": {
|
||||
"title": "注意",
|
||||
"message": {
|
||||
"none": "它之前<span class=\"label label-important\">从未</span>被批准。",
|
||||
"singular": "它之前已被批准了<span class=\"label label-warning\">{0}</span>次。",
|
||||
"plural": "它之前已被批准了<span class=\"label\">{0}</span>次。"
|
||||
}
|
||||
},
|
||||
"more_information": "更多信息",
|
||||
"home_page": "主页",
|
||||
"policy": "政策",
|
||||
"terms": "服务条款",
|
||||
"contacts": "管理人员",
|
||||
"warning": "警告",
|
||||
"no_redirect_uri": "该客户端没有注册任何重定向URI,可能被使用恶意的URI。",
|
||||
"redirect_uri": "如果点击批准,您将被重定向至如下页面: <code>{0}</code>",
|
||||
"pairwise": "该客户端使用<b>pairwise</b>标识符,这使得在不同站点间关联身份变得稍加困难。",
|
||||
"no_scopes": "该客户端没有注册任何范围,因此允许请求系统可用的<em>any</em>(任意)范围。请务必谨慎处理。",
|
||||
"access_to": "访问",
|
||||
"remember": {
|
||||
"title": "记住这个决定",
|
||||
"until_revoke": "记住这个决定直到我撤销",
|
||||
"one_hour": "记住该决定一个小时",
|
||||
"next_time": "下次再提醒我"
|
||||
},
|
||||
"do_authorize": "是否授权",
|
||||
"label": {
|
||||
"authorize": "授权",
|
||||
"deny": "拒绝"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"title": "错误",
|
||||
"header": "错误:",
|
||||
"message": "在处理您的请求过程中发生了错误。服务器信息为:"
|
||||
},
|
||||
"login": {
|
||||
"login_with_username_and_password": "请使用您的用户名及密码登录",
|
||||
"username": "用户名",
|
||||
"password": "密码",
|
||||
"login-button": "登录",
|
||||
"error": "登录失败。请重试。"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,490 @@
|
|||
{
|
||||
"admin": {
|
||||
"blacklist": "黑名单",
|
||||
"blacklist-form": {
|
||||
"blacklisted-uris": "列入黑名单的URI"
|
||||
},
|
||||
"home": "首页",
|
||||
"list-widget": {
|
||||
"empty": "此列表为空。",
|
||||
"tooltip": "单击显示全部值。"
|
||||
},
|
||||
"manage-blacklist": "管理列入黑名单的客户端",
|
||||
"self-service-client": "自助服务-客户端注册",
|
||||
"self-service-resource": "自助服务-受保护资源注册",
|
||||
"user-profile": {
|
||||
"claim": "声明项",
|
||||
"show": "查看用户信息",
|
||||
"text": "您的用户信息如下:",
|
||||
"value": "内容"
|
||||
}
|
||||
},
|
||||
"client": {
|
||||
"client-form": {
|
||||
"access": "访问",
|
||||
"access-token-no-timeout": "访问令牌不时间",
|
||||
"access-token-timeout": "访问令牌超时",
|
||||
"access-token-timeout-help": "输入时间(秒、分钟或小时)。",
|
||||
"acr-values": "默认ACR值",
|
||||
"acr-values-placeholder": "新的ACR值",
|
||||
"acr-values-help": "用于请求该客户端的默认身份验证上下文参考",
|
||||
"allow-introspection": "允许调用内省端点?",
|
||||
"authentication-method": "令牌端点认证方法",
|
||||
"authorization-code": "授权码",
|
||||
"client-credentials": "客户端凭证",
|
||||
"client-description": "描述",
|
||||
"client-description-help": "人类可读的文本描述",
|
||||
"client-description-placeholder": "填入说明描述",
|
||||
"client-id": "客户端ID",
|
||||
"client-id-help": "唯一标识符。如果不填则系统会自动生成一个。",
|
||||
"client-id-placeholder": "输入一些字符",
|
||||
"client-name": "客户端名称",
|
||||
"client-name-help": "人类可读的应用程序名称",
|
||||
"client-name-placeholder": "输入一些字符",
|
||||
"client-secret": "客户端密钥",
|
||||
"client-secret-placeholder": "输入密钥",
|
||||
"contacts": "联系人",
|
||||
"contacts-help": "此客户端管理员的联系人名单。",
|
||||
"contacts-placeholder": "新联系人",
|
||||
"credentials": "凭据",
|
||||
"crypto": {
|
||||
"a128cbc-hs256": "复合认证加密算法,采用密码块链(CBC)模式AES,以PKCS #5填充,完整性计算使用HMAC SHA-256,并使用256位的CMK(和128位CEK)",
|
||||
"a256cbc-hs512": "复合认证加密算法,采用密码块链(CBC)模式AES,以PKCS #5填充,完整性计算使用HMAC SHA-512,并使用512位的CMK(和256位CEK)",
|
||||
"a128gcm": "AES GCM使用128位的密钥",
|
||||
"a256gcm": "AES GCM使用256位的密钥",
|
||||
"a128kw": "AES密钥封装算法使用128位的密钥",
|
||||
"a256kw": "AES密钥封装算法使用256位的密钥",
|
||||
"default": "使用服务器默认",
|
||||
"dir": "直接使用一个共享对称密钥作为块加密的内容主密钥(CMK)",
|
||||
"ecdh-es": "椭圆曲线Diffie-Hellman短时静态密钥协议(使用Concat KDF),商定的密钥被直接用作内容主密钥(CMK)",
|
||||
"ecdh-es-a128kw": "椭圆曲线Diffie-Hellman短时静态密钥协议(使用ECDH-ES和第4.7小节),但商定的密钥是用以A128KW函数封装内容主密钥(CMK)",
|
||||
"ecdh-es-a256kw": "椭圆曲线Diffie-Hellman短时静态密钥协议(使用ECDH-ES和第4.7小节),但商定的密钥是用以A256KW函数封装内容主密钥(CMK)",
|
||||
"none": "不加密",
|
||||
"rsa-oaep": "RSAES使用最优不对称加密填充(OAEP)",
|
||||
"rsa1-5": "RSAES-PKCS1-V1_5"
|
||||
},
|
||||
"cryptography": "密码",
|
||||
"display-secret": "显示/编辑客户端密钥:",
|
||||
"edit": "编辑客户端",
|
||||
"generate-new-secret": "生成一个新的客户端密钥吗?",
|
||||
"generate-new-secret-help": "当点击“保存”时生成新的密钥",
|
||||
"generate-on-save": "保存时生成",
|
||||
"grant-types": "批准的类型",
|
||||
"home": "主页",
|
||||
"home-help": "客户端首页的URL,将显示给用户",
|
||||
"hours": "小时",
|
||||
"id": "ID:",
|
||||
"id-token-crypto-algorithm": "身份令牌加密算法",
|
||||
"id-token-crypto-method": "身份令牌加密方法",
|
||||
"id-token-signing-algorithm": "身份令牌签名算法",
|
||||
"id-token-timeout": "身份令牌超时",
|
||||
"implicit": "隐式的",
|
||||
"initiate-login": "初始化登录",
|
||||
"initiate-login-help": "启动登录客户端的URL",
|
||||
"introspection": "自省",
|
||||
"jwk-set": "公钥集",
|
||||
"jwk-set-help": "客户端JSON Web Key集的URL (须可被服务器访问)",
|
||||
"jwk-set-value-help": "客户端JSON Web Key集的URL (须可被服务器访问)",
|
||||
"logo": "标志(Logo)",
|
||||
"logo-help": "标志(Logo)图像的URL,将显示在批准页",
|
||||
"main": "首要",
|
||||
"max-age": "默认最长有效时间",
|
||||
"max-age-help": "再提示之前的默认最长会话有效时间",
|
||||
"minutes": "分钟",
|
||||
"new": "新客户端",
|
||||
"other": "其它",
|
||||
"pairwise": "Pairwise对",
|
||||
"password": "密码",
|
||||
"policy": "政策声明",
|
||||
"policy-help": "此客户端的政策声明链接,将显示给用户",
|
||||
"post-logout": "注销后重定向",
|
||||
"post-logout-help": "客户端注销操作后的重定向URL",
|
||||
"public": "公共",
|
||||
"redelegation": "重新授权",
|
||||
"redirect-uris": "重定向URI",
|
||||
"redirect-uris-help": "在授权页面之后客户端重定向URI",
|
||||
"claims-redirect-uris": "声明重定向URI",
|
||||
"claims-redirect-uris-help": "在声明收集步骤之后浏览器跳转至的目的地址",
|
||||
"refresh": "刷新",
|
||||
"refresh-tokens": "刷新令牌",
|
||||
"refresh-tokens-issued": "为此客户端发布的刷新令牌",
|
||||
"refresh-tokens-issued-help": "这将把 offline_access 加入客户端的范围。",
|
||||
"refresh-tokens-reused": "此客户端的刷新令牌被重用",
|
||||
"clear-access-tokens": "当刷新令牌用过之后,已激活的访问令牌自动失效",
|
||||
"refresh-tokens-no-expire": "刷新令牌尚未过期",
|
||||
"registered": "注册于",
|
||||
"registration-token": "注册令牌:",
|
||||
"registration-access-token": "注册访问令牌",
|
||||
"registration-token-error": "无法为此客户端下载注册访问令牌。",
|
||||
"request-object-signing-algorithm": "请求对象签名算法",
|
||||
"request-uri": "请求的URI",
|
||||
"request-uri-help": "URI包含此客户端使用的请求对象",
|
||||
"require-auth-time": "需要身份认证时间(auth_time)",
|
||||
"require-auth-time-label": "总是需要在身份令牌中包含auth_time声明",
|
||||
"response-types": "响应类型",
|
||||
"rotate-registration-token": "旋转注册令牌",
|
||||
"rotate-registration-token-confirm": "你确定你想旋转这个客户端的登录令牌?",
|
||||
"rotate-registration-token-error": "无法旋转该客户端的注册访问令牌。",
|
||||
"saved": {
|
||||
"no-secret": "没有客户端密钥",
|
||||
"saved": "客户端已保存",
|
||||
"secret": "密钥:",
|
||||
"show-secret": "显示密钥",
|
||||
"unchanged": "不变"
|
||||
},
|
||||
"scope-placeholder": "新范围",
|
||||
"scope-help": "OAuth范围允许客户端请求",
|
||||
"seconds": "秒",
|
||||
"secret-asymmetric-jwt": "非对称签名JWT断言",
|
||||
"secret-http": "客户端密钥经由HTTP Basic",
|
||||
"secret-none": "没有认证",
|
||||
"secret-post": "客户端密钥经由HTTP POST",
|
||||
"secret-symmetric-jwt": "客户端密钥经由对称签名JWT断言",
|
||||
"sector-identifier": "扇区标识符URI",
|
||||
"signing": {
|
||||
"any": "允许",
|
||||
"default": "使用服务器默认",
|
||||
"es256": "ECDSA采用P-256曲线和SHA-256哈希算法",
|
||||
"es384": "ECDSA采用P-384曲线及SHA-384哈希算法",
|
||||
"es512": "ECDSA采用P-512曲线及SHA-512哈希算法",
|
||||
"hs256": "HMAC使用SHA-256哈希算法",
|
||||
"hs384": "HMAC使用SHA-384哈希算法",
|
||||
"hs512": "HMAC使用SHA-512哈希算法",
|
||||
"none": "没有数字签名",
|
||||
"rs256": "RSASSA使用SHA-256哈希算法",
|
||||
"rs384": "RSASSA采用SHA-384哈希算法",
|
||||
"rs512": "RSASSA使用SHA-512哈希算法",
|
||||
"ps256": "采用SHA-256和MGF1的RSASSA-PSS算法",
|
||||
"ps384": "采用SHA-384和MGF1的RSASSA-PSS算法",
|
||||
"ps512": "采用SHA-512和MGF1的RSASSA-PSS算法"
|
||||
},
|
||||
"subject-type": "主体类型",
|
||||
"terms": "服务条款",
|
||||
"terms-help": "此客户服务条款的URL,将向用户显示",
|
||||
"token-signing-algorithm": "令牌端点认证签名算法",
|
||||
"tokens": "令牌",
|
||||
"type": "应用类型",
|
||||
"type-native": "原生应用",
|
||||
"type-web": "网络应用",
|
||||
"unknown": "(未知)",
|
||||
"user-info-crypto-algorithm": "用户信息端点加密算法",
|
||||
"user-info-crypto-method": "用户信息端点加密方法",
|
||||
"user-info-signing-algorithm": "用户信息端点签名算法"
|
||||
},
|
||||
"client-table": {
|
||||
"allow-introspection-tooltip": "这个客户端可以执行令牌自省",
|
||||
"confirm": "你确定要删除这个客户端?",
|
||||
"dynamically-registered-tooltip": "这个客户端是动态注册的。点击查看注册访问令牌",
|
||||
"match": {
|
||||
"contacts": "联系人",
|
||||
"description": "描述",
|
||||
"homepage": "主页",
|
||||
"id": "身份",
|
||||
"logo": "标志",
|
||||
"name": "名称",
|
||||
"policy": "政策",
|
||||
"redirect": "重定向URI",
|
||||
"scope": "范围",
|
||||
"terms": "服务条款"
|
||||
},
|
||||
"matched-search": "匹配搜索:",
|
||||
"new": "新客户端",
|
||||
"no-clients": "此服务器上没有注册的客户端。",
|
||||
"no-matches": "没有匹配搜索条件的客户端。",
|
||||
"no-redirect": "没有重定向URI",
|
||||
"registered": "注册于",
|
||||
"search": "搜索……",
|
||||
"whitelist": "白名单",
|
||||
"unknown": "一个未知的时间"
|
||||
},
|
||||
"manage": "管理客户端",
|
||||
"more-info": {
|
||||
"contacts": "管理员联系方式:",
|
||||
"home": "主页",
|
||||
"more": "更多信息",
|
||||
"policy": "政策",
|
||||
"terms": "服务条款:"
|
||||
},
|
||||
"newClient": "新客户端"
|
||||
},
|
||||
"common": {
|
||||
"cancel": "取消",
|
||||
"client": "客户端",
|
||||
"clients": "客户端",
|
||||
"close": "关闭",
|
||||
"delete": "删除",
|
||||
"description": "描述",
|
||||
"dynamically-registered": "这个客户端是动态注册的",
|
||||
"edit": "编辑",
|
||||
"expires": "到期:",
|
||||
"information": "信息",
|
||||
"new": "新建",
|
||||
"not-yet-implemented": "未实现",
|
||||
"not-yet-implemented-content": "这个字段的值将于客户端保存,但服务器目前不处理任何事情。服务器的未来库版本将利用它。",
|
||||
"revoke": "撤销",
|
||||
"save": "保存",
|
||||
"scopes": "范围",
|
||||
"statistics": "统计",
|
||||
"refresh": "刷新",
|
||||
"scope": "范围",
|
||||
"users": "用户",
|
||||
"user": "用户",
|
||||
"roles": "角色",
|
||||
"role": "角色",
|
||||
"email": "电子邮箱",
|
||||
"active": "已激活",
|
||||
"inactive": "未激活"
|
||||
},
|
||||
"dynreg": {
|
||||
"client-id-placeholder": "输入客户端ID",
|
||||
"configuration-url": "客户端配置URL",
|
||||
"edit-dynamically-registered": "编辑动态注册的客户端",
|
||||
"edit-existing": "编辑一个现有的客户端",
|
||||
"edit-existing-help": "用于编辑之前已注册的客户端。粘贴您的客户端ID和注册访问令牌,以便访问该客户端。",
|
||||
"edit-existing-button": "编辑客户端",
|
||||
"invalid-access-token": "无效的客户端或注册访问令牌。",
|
||||
"new-client": "注册新客户端",
|
||||
"new-client-help": "用于注册新的客户端。请提供客户端ID和注册访问令牌,以便管理您的客户端。",
|
||||
"new-client-button": "新建客户端",
|
||||
"regtoken-placeholder": "输入注册访问令牌",
|
||||
"warning": "<strong>警告!</strong>你必须保护好<b>客户端ID </b>,<b>客户密钥(如果提供)</b>,以及您的<b>注册访问令牌</b>。如果你丢失了客户端ID或注册访问令牌,将无法访问您的客户端注册记录,你需要注册一个新客户端。",
|
||||
"will-be-generated": "当保存客户端信息将由服务器生成"
|
||||
},
|
||||
"grant": {
|
||||
"manage-approved-sites": "管理批准的网站",
|
||||
"refresh": "刷新",
|
||||
"grant-table": {
|
||||
"active-tokens": "当前活跃的访问令牌数量",
|
||||
"application": "应用程序",
|
||||
"approved-sites": "许可站点",
|
||||
"authorized": "授权:",
|
||||
"dynamically-registered": "这个客户端是动态注册的",
|
||||
"expires": "到期:",
|
||||
"last-accessed": "上次访问:",
|
||||
"never": "从未",
|
||||
"no-sites": "还未批准任何网站。",
|
||||
"no-whitelisted": "还未访问任何白名单的网站。",
|
||||
"pre-approved": "这些都是预先由管理员批准的网站。",
|
||||
"text": "这些都是您已经手动批准的网站。如果同一网站将来要进行同样的访问,它将直接通过、且没有提示。",
|
||||
"unknown": "未知",
|
||||
"whitelist-note": "<b>注:</b>如果你在此撤销它们,它们将在您下次访问时不经提示即被自动重新批准。",
|
||||
"whitelisted-site": "这个网站由管理员列入白名单中",
|
||||
"whitelisted-sites": "白名单的网站"
|
||||
}
|
||||
},
|
||||
"rsreg": {
|
||||
"resource-id-placeholder": "输入资源ID",
|
||||
"configuration-url": "客户端配置URL",
|
||||
"edit": "编辑受保护的资源",
|
||||
"edit-existing": "编辑现有的保护资源",
|
||||
"edit-existing-help": "用于编辑之前已注册的资源。请提供您的客戶端ID和注册访问令牌来访问资源的属性。",
|
||||
"edit-existing-button": "编辑资源",
|
||||
"invalid-access-token": "无效的客户端或注册访问令牌。",
|
||||
"new-client": "注册新的受保护资源",
|
||||
"new-client-help": "用于注册新的资源。请提供客户端ID和注册访问令牌,以便管理您的资源。",
|
||||
"new-client-button": "新建资源",
|
||||
"regtoken-placeholder": "输入注册访问令牌",
|
||||
"will-be-generated": "当保存资源信息将由服务器生成",
|
||||
"warning": "<strong>警告!</strong>你必须保护好<b>客户端ID </b>,<b>客户密钥(如果提供)</b>,以及<b>注册访问令牌</b>。如果丢失了客户端ID或注册访问令牌,将无法再次获得您的客户端注册记录,你需要注册一个新客户端。",
|
||||
"client-form": {
|
||||
"scope-help": "这个资源能够自省令牌的范围。"
|
||||
}
|
||||
},
|
||||
"scope": {
|
||||
"manage": "管理系统范围",
|
||||
"scope-list": {
|
||||
"no-scopes": "没有范围"
|
||||
},
|
||||
"system-scope-form": {
|
||||
"default": "默认范围",
|
||||
"default-help": "新创建的用户默认情况下获得这个范围?",
|
||||
"description-help": "人类可读的文本描述",
|
||||
"description-placeholder": "输入说明",
|
||||
"restricted": "限制",
|
||||
"restricted-help": "限制范围只能由系统管理员使用,可用动态注册客户和保护资源",
|
||||
"edit": "编辑范围",
|
||||
"icon": "图标",
|
||||
"new": "新范围",
|
||||
"select-icon": "选择图标",
|
||||
"structured": "是一个结构化的范围",
|
||||
"structured-help": "范围结构化是否包含如<code>base:extension</code>的结构化值?",
|
||||
"structured-param-help": "人类可读的结构化参数描述",
|
||||
"subject-type": "主体类型",
|
||||
"value": "范围值",
|
||||
"value-help": "不含空格的单个字符串",
|
||||
"value-placeholder": "范围"
|
||||
},
|
||||
"system-scope-table": {
|
||||
"confirm": "你确定要删除此范围?引用了此范围的客户端还需要它。",
|
||||
"new": "新范围",
|
||||
"text": "尚未定义系统范围。客户可自定义范围。",
|
||||
"tooltip-restricted": "此范围只能由管理员使用。它不能用于动态注册。",
|
||||
"tooltip-default": "这个范围将自动分配给新注册的客户。"
|
||||
}
|
||||
},
|
||||
"token": {
|
||||
"manage": "管理活动的令牌",
|
||||
"token-table": {
|
||||
"access-tokens": "访问令牌",
|
||||
"associated-id": "这个访问令牌附带相关的身份令牌。",
|
||||
"associated-refresh": "这个访问令牌附带相关的刷新令牌。",
|
||||
"click-to-display": "点击显示完整的令牌值",
|
||||
"confirm": "你确定要撤销这个令牌?",
|
||||
"confirm-refresh": "你确定要撤销这个刷新令牌及其相关的访问令牌?",
|
||||
"expires": "过期",
|
||||
"no-access": "没有活动的访问令牌。",
|
||||
"no-refresh": "没有活动的刷新令牌。",
|
||||
"number-of-tokens": "关联的访问令牌数量",
|
||||
"refresh-tokens": "刷新令牌",
|
||||
"text": "访问令牌通常是短暂的,供客户端访问特定的资源。身份令牌是采用OpenID Connect协议登录的、专门的访问令牌。",
|
||||
"text-refresh": "刷新令牌通常是长期的,以便客户端能无需用户介入即可获取新的访问令牌。",
|
||||
"token-info": "令牌的信息"
|
||||
}
|
||||
},
|
||||
"whitelist": {
|
||||
"confirm": "你确定要删除这个白名单项?",
|
||||
"edit": "编辑白名单",
|
||||
"manage": "管理列入白名单的网站",
|
||||
"new": "新白名单",
|
||||
"whitelist": "白名单",
|
||||
"whitelist-form": {
|
||||
"allowed-scopes": "允许范围",
|
||||
"edit": "编辑白名单的网站",
|
||||
"new": "新增白名单网站",
|
||||
"scope-help": "当客户端发出请求列表时将自动批准的范围",
|
||||
"scope-placeholder": "新范围"
|
||||
},
|
||||
"whitelist-table": {
|
||||
"no-sites": "白名单列表为空。使用<strong>白名单</strong>按钮在客户端管理页面创建一个。"
|
||||
}
|
||||
},
|
||||
"blacklist": {
|
||||
"text": "被拉黑的网站URI将无法用做注册客户端的重定向地址(无论是在管理界面中添加、还是动态注册,都不会成功)。",
|
||||
"blacklist-uri-placeholder": "要拉黑的网站URI",
|
||||
"add": "将网站URI加入黑名单",
|
||||
"empty": "当前黑名单为空",
|
||||
"uri": "URI"
|
||||
},
|
||||
"copyright": "基于<a href=\"https://github.com/mitreid-connect/\">MITREid Connect <span class=\"label\">{0}</span></a>技术构建 <span class=\"pull-right\">© 2016 MITRE公司及MIT因特网信任联盟</span>.",
|
||||
"about": {
|
||||
"title": "关于",
|
||||
"body": "\n此OpenID Connect服务基于开源的MITREid Connect项目,该项目来自 \n<a href=\"http://www.mitre.org/\">MITRE公司</a> 及 <a href=\"http://kit.mit.edu/\">MIT因特网信任联盟</a>。\n</p>\n<p>\n有关项目的更多信息可见 \n<a href=\"http://github.com/mitreid-connect/\">GitHub上的MITREid Connect项目</a>。 \n在那儿,您可以提交bug报告、提交反馈甚或提交代码补丁。"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "统计",
|
||||
"number_users": "用户数: <span class=\"label label-info\" id=\"userCount\">{0}</span>",
|
||||
"number_clients": "授权的客户端: <span class=\"label label-info\" id=\"clientCount\">{0}</span>",
|
||||
"number_approvals": "已批准的站点: <span class=\"label label-info\" id=\"approvalCount\">{0}</span>"
|
||||
},
|
||||
"home": {
|
||||
"title": "首页",
|
||||
"welcome": {
|
||||
"title": "欢迎!",
|
||||
"body": "\nOpenID Connect是适于因特网部署的身份联邦认证服务器,基于OAuth2授权框架之上的OpenID Connect技术构建。\nOpenID Connect让您无需暴露自己的用户名、密码即可便捷登录网站。</p>\n<p><a class=\"btn btn-primary btn-large\" href=\"http://openid.net/connect/\">了解更多信息»</a>"
|
||||
},
|
||||
"more": "更多",
|
||||
"about": {
|
||||
"title": "关于",
|
||||
"body": "本服务基于开源的MITREid Connect项目,该项目来自 \n<a href=\"http://www.mitre.org/\">MITRE公司</a> 及 <a href=\"http://kit.mit.edu/\">MIT因特网信任联盟</a>。"
|
||||
},
|
||||
"contact": {
|
||||
"title": "联系方式",
|
||||
"body": "\n如需更多的信息和支持,请联系本系统的管理员。</p>\n<p><a class=\"btn\" href=\"mailto:idp@example.com?Subject=OpenID Connect\">电子信箱 »</a>"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "当前统计",
|
||||
"loading": "加载……",
|
||||
"number_users": "用户数: <span class=\"label label-info\" id=\"userCount\">{0}</span>",
|
||||
"number_clients": "授权的客户端: <span class=\"label label-info\" id=\"clientCount\">{0}</span>",
|
||||
"number_approvals": "已批准的站点: <span class=\"label label-info\" id=\"approvalCount\">{0}</span>"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"title": "联系方式",
|
||||
"body": "如果要报告有关MITREid Connect软件自身的bug,请访问\n<a href=\"https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/issues\">GitHub issue追踪系统</a>。 \n有关当前服务器的问题,请联系服务器管理员。"
|
||||
},
|
||||
"topbar": {
|
||||
"about": "关于",
|
||||
"contact": "联系方式",
|
||||
"statistics": "统计",
|
||||
"home": "首页",
|
||||
"login": "登录",
|
||||
"logout": "注销"
|
||||
},
|
||||
"sidebar": {
|
||||
"administrative": {
|
||||
"title": "管理",
|
||||
"manage_clients": "管理客户端",
|
||||
"whitelisted_clients": "白名单",
|
||||
"blacklisted_clients": "黑名单",
|
||||
"system_scopes": "系统范围"
|
||||
},
|
||||
"personal": {
|
||||
"title": "个人",
|
||||
"approved_sites": "管理批准的网站",
|
||||
"active_tokens": "管理活动的令牌",
|
||||
"profile_information": "查看用户信息"
|
||||
},
|
||||
"developer": {
|
||||
"title": "开发者自助服务",
|
||||
"client_registration": "客户端注册",
|
||||
"resource_registration": "保护资源注册"
|
||||
}
|
||||
},
|
||||
"manage": {
|
||||
"ok": "好的",
|
||||
"loading": "加载",
|
||||
"title": "管理控制台"
|
||||
},
|
||||
"approve": {
|
||||
"dynamically-registered-unknown": "在一个未知的时间",
|
||||
"title": "批准访问",
|
||||
"error": {
|
||||
"not_granted": "访问可能不获批准。"
|
||||
},
|
||||
"required_for": "有待批准",
|
||||
"dynamically_registered": "此客户端已被动态注册了<span class=\"label label-info\" id=\"registrationTime\">{0}</span>次。",
|
||||
"caution": {
|
||||
"title": "注意",
|
||||
"message": {
|
||||
"none": "它之前<span class=\"label label-important\">从未</span>被批准。",
|
||||
"singular": "它之前已被批准了<span class=\"label label-warning\">{0}</span>次。",
|
||||
"plural": "它之前已被批准了<span class=\"label\">{0}</span>次。"
|
||||
}
|
||||
},
|
||||
"more_information": "更多信息",
|
||||
"home_page": "主页",
|
||||
"policy": "政策",
|
||||
"terms": "服务条款",
|
||||
"contacts": "管理人员",
|
||||
"warning": "警告",
|
||||
"no_redirect_uri": "该客户端没有注册任何重定向URI,可能被使用恶意的URI。",
|
||||
"redirect_uri": "如果点击批准,您将被重定向至如下页面: <code>{0}</code>",
|
||||
"pairwise": "该客户端使用<b>pairwise</b>标识符,这使得在不同站点间关联身份变得稍加困难。",
|
||||
"no_scopes": "该客户端没有注册任何范围,因此允许请求系统可用的<em>any</em>(任意)范围。请务必谨慎处理。",
|
||||
"access_to": "访问",
|
||||
"remember": {
|
||||
"title": "记住这个决定",
|
||||
"until_revoke": "记住这个决定直到我撤销",
|
||||
"one_hour": "记住该决定一个小时",
|
||||
"next_time": "下次再提醒我"
|
||||
},
|
||||
"do_authorize": "是否授权",
|
||||
"label": {
|
||||
"authorize": "授权",
|
||||
"deny": "拒绝"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"title": "错误",
|
||||
"header": "错误:",
|
||||
"message": "在处理您的请求过程中发生了错误。服务器信息为:"
|
||||
},
|
||||
"login": {
|
||||
"login_with_username_and_password": "请使用您的用户名及密码登录",
|
||||
"username": "用户名",
|
||||
"password": "密码",
|
||||
"login-button": "登录",
|
||||
"error": "登录失败。请重试。"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,490 @@
|
|||
{
|
||||
"admin": {
|
||||
"blacklist": "黑名單",
|
||||
"blacklist-form": {
|
||||
"blacklisted-uris": "列入黑名單的URI"
|
||||
},
|
||||
"home": "首頁",
|
||||
"list-widget": {
|
||||
"empty": "此列表為空。",
|
||||
"tooltip": "單擊顯示全部值。"
|
||||
},
|
||||
"manage-blacklist": "管理列入黑名單的客戶端",
|
||||
"self-service-client": "自助服務-客戶端註冊",
|
||||
"self-service-resource": "自助服務-受保護資源註冊",
|
||||
"user-profile": {
|
||||
"claim": "聲明項",
|
||||
"show": "查看用戶資訊",
|
||||
"text": "您的用戶資訊如下:",
|
||||
"value": "內容"
|
||||
}
|
||||
},
|
||||
"client": {
|
||||
"client-form": {
|
||||
"access": "訪問",
|
||||
"access-token-no-timeout": "訪問令牌不時間",
|
||||
"access-token-timeout": "訪問令牌超時",
|
||||
"access-token-timeout-help": "輸入時間(秒、分鐘或小時)。",
|
||||
"acr-values": "默認ACR值",
|
||||
"acr-values-placeholder": "新的ACR值",
|
||||
"acr-values-help": "用於請求該客戶端的默認身份驗證上下文參考",
|
||||
"allow-introspection": "允許調用內省端點?",
|
||||
"authentication-method": "令牌端點認證方法",
|
||||
"authorization-code": "授權碼",
|
||||
"client-credentials": "客戶端憑證",
|
||||
"client-description": "描述",
|
||||
"client-description-help": "人類可讀的文本描述",
|
||||
"client-description-placeholder": "填入說明描述",
|
||||
"client-id": "客戶端ID",
|
||||
"client-id-help": "唯一標識符。如果不填則系統會自動生成一個。",
|
||||
"client-id-placeholder": "輸入一些字符",
|
||||
"client-name": "客戶端名稱",
|
||||
"client-name-help": "人類可讀的應用程式名稱",
|
||||
"client-name-placeholder": "輸入一些字符",
|
||||
"client-secret": "客戶端密鑰",
|
||||
"client-secret-placeholder": "輸入密鑰",
|
||||
"contacts": "聯繫人",
|
||||
"contacts-help": "此客戶端管理員的聯繫人名單。",
|
||||
"contacts-placeholder": "新聯繫人",
|
||||
"credentials": "憑據",
|
||||
"crypto": {
|
||||
"a128cbc-hs256": "複合認證加密算法,採用密碼塊鏈(CBC)模式AES,以PKCS #5填充,完整性計算使用HMAC SHA-256,並使用256位的CMK(和128位CEK)",
|
||||
"a256cbc-hs512": "複合認證加密算法,採用密碼塊鏈(CBC)模式AES,以PKCS #5填充,完整性計算使用HMAC SHA-512,並使用512位的CMK(和256位CEK)",
|
||||
"a128gcm": "AES GCM使用128位的密鑰",
|
||||
"a256gcm": "AES GCM使用256位的密鑰",
|
||||
"a128kw": "AES密鑰封裝算法使用128位的密鑰",
|
||||
"a256kw": "AES密鑰封裝算法使用256位的密鑰",
|
||||
"default": "使用伺服器默認",
|
||||
"dir": "直接使用一個共享對稱密鑰作為塊加密的內容主密鑰(CMK)",
|
||||
"ecdh-es": "橢圓曲線Diffie-Hellman短時靜態密鑰協議(使用Concat KDF),商定的密鑰被直接用作內容主密鑰(CMK)",
|
||||
"ecdh-es-a128kw": "橢圓曲線Diffie-Hellman短時靜態密鑰協議(使用ECDH-ES和第4.7小節),但商定的密鑰是用以A128KW函數封裝內容主密鑰(CMK)",
|
||||
"ecdh-es-a256kw": "橢圓曲線Diffie-Hellman短時靜態密鑰協議(使用ECDH-ES和第4.7小節),但商定的密鑰是用以A256KW函數封裝內容主密鑰(CMK)",
|
||||
"none": "不加密",
|
||||
"rsa-oaep": "RSAES使用最優不對稱加密填充(OAEP)",
|
||||
"rsa1-5": "RSAES-PKCS1-V1_5"
|
||||
},
|
||||
"cryptography": "密碼",
|
||||
"display-secret": "顯示/編輯客戶端密鑰:",
|
||||
"edit": "編輯客戶端",
|
||||
"generate-new-secret": "生成一個新的客戶端密鑰嗎?",
|
||||
"generate-new-secret-help": "當點擊「保存」時生成新的密鑰",
|
||||
"generate-on-save": "保存時生成",
|
||||
"grant-types": "批准的類型",
|
||||
"home": "主頁",
|
||||
"home-help": "客戶端首頁的URL,將顯示給用戶",
|
||||
"hours": "小時",
|
||||
"id": "ID:",
|
||||
"id-token-crypto-algorithm": "身份令牌加密算法",
|
||||
"id-token-crypto-method": "身份令牌加密方法",
|
||||
"id-token-signing-algorithm": "身份令牌簽名算法",
|
||||
"id-token-timeout": "身份令牌超時",
|
||||
"implicit": "隱式的",
|
||||
"initiate-login": "初始化登入",
|
||||
"initiate-login-help": "啟動登入客戶端的URL",
|
||||
"introspection": "自省",
|
||||
"jwk-set": "公鑰集",
|
||||
"jwk-set-help": "客戶端JSON Web Key集的URL (須可被伺服器訪問)",
|
||||
"jwk-set-value-help": "客戶端JSON Web Key集的URL (須可被伺服器訪問)",
|
||||
"logo": "標誌(Logo)",
|
||||
"logo-help": "標誌(Logo)圖像的URL,將顯示在批准頁",
|
||||
"main": "首要",
|
||||
"max-age": "默認最長有效時間",
|
||||
"max-age-help": "再提示之前的默認最長會話有效時間",
|
||||
"minutes": "分鐘",
|
||||
"new": "新客戶端",
|
||||
"other": "其它",
|
||||
"pairwise": "Pairwise對",
|
||||
"password": "密碼",
|
||||
"policy": "政策聲明",
|
||||
"policy-help": "此客戶端的政策聲明連接,將顯示給用戶",
|
||||
"post-logout": "登出後重定向",
|
||||
"post-logout-help": "客戶端登出操作後的重定向URL",
|
||||
"public": "公共",
|
||||
"redelegation": "重新授權",
|
||||
"redirect-uris": "重定向URI",
|
||||
"redirect-uris-help": "在授權頁面之後客戶端重定向URI",
|
||||
"claims-redirect-uris": "聲明重定向URI",
|
||||
"claims-redirect-uris-help": "在聲明採集步驟之後瀏覽器需重定向的目標URI",
|
||||
"refresh": "刷新",
|
||||
"refresh-tokens": "刷新令牌",
|
||||
"refresh-tokens-issued": "為此客戶端發佈的刷新令牌",
|
||||
"refresh-tokens-issued-help": "這將把 offline_access 加入客戶端的範圍。",
|
||||
"refresh-tokens-reused": "此客戶端的刷新令牌被重用",
|
||||
"clear-access-tokens": "當刷新令牌用過之後,已激活的訪問令牌自動失效",
|
||||
"refresh-tokens-no-expire": "刷新令牌尚未過期",
|
||||
"registered": "註冊於",
|
||||
"registration-token": "註冊令牌:",
|
||||
"registration-access-token": "註冊訪問令牌",
|
||||
"registration-token-error": "無法為此客戶端下載註冊訪問令牌。",
|
||||
"request-object-signing-algorithm": "請求對像簽名算法",
|
||||
"request-uri": "請求的URI",
|
||||
"request-uri-help": "URI包含此客戶端使用的請求對像",
|
||||
"require-auth-time": "需要身份認證時間(auth_time)",
|
||||
"require-auth-time-label": "總是需要在身份令牌中包含auth_time聲明",
|
||||
"response-types": "回應類型",
|
||||
"rotate-registration-token": "旋轉註冊令牌",
|
||||
"rotate-registration-token-confirm": "你確定你想旋轉這個客戶端的登入令牌?",
|
||||
"rotate-registration-token-error": "無法旋轉該客戶端的註冊訪問令牌。",
|
||||
"saved": {
|
||||
"no-secret": "沒有客戶端密鑰",
|
||||
"saved": "客戶端已保存",
|
||||
"secret": "密鑰:",
|
||||
"show-secret": "顯示密鑰",
|
||||
"unchanged": "不變"
|
||||
},
|
||||
"scope-placeholder": "新範圍",
|
||||
"scope-help": "OAuth範圍允許客戶端請求",
|
||||
"seconds": "秒",
|
||||
"secret-asymmetric-jwt": "非對稱簽名JWT斷言",
|
||||
"secret-http": "客戶端密鑰經由HTTP Basic",
|
||||
"secret-none": "沒有認證",
|
||||
"secret-post": "客戶端密鑰經由HTTP POST",
|
||||
"secret-symmetric-jwt": "客戶端密鑰經由對稱簽名JWT斷言",
|
||||
"sector-identifier": "扇區標識符URI",
|
||||
"signing": {
|
||||
"any": "允許",
|
||||
"default": "使用伺服器默認",
|
||||
"es256": "ECDSA採用P-256曲線和SHA-256哈希算法",
|
||||
"es384": "ECDSA採用P-384曲線及SHA-384哈希算法",
|
||||
"es512": "ECDSA採用P-512曲線及SHA-512哈希算法",
|
||||
"hs256": "HMAC使用SHA-256哈希算法",
|
||||
"hs384": "HMAC使用SHA-384哈希算法",
|
||||
"hs512": "HMAC使用SHA-512哈希算法",
|
||||
"none": "沒有數字簽名",
|
||||
"rs256": "RSASSA使用SHA-256哈希算法",
|
||||
"rs384": "RSASSA採用SHA-384哈希算法",
|
||||
"rs512": "RSASSA使用SHA-512哈希算法",
|
||||
"ps256": "採用SHA-256和MGF1的RSASSA-PSS算法",
|
||||
"ps384": "採用SHA-384和MGF1的RSASSA-PSS算法",
|
||||
"ps512": "採用SHA-512和MGF1的RSASSA-PSS算法"
|
||||
},
|
||||
"subject-type": "主體類型",
|
||||
"terms": "服務條款",
|
||||
"terms-help": "此客戶服務條款的URL,將向用戶顯示",
|
||||
"token-signing-algorithm": "令牌端點認證簽名算法",
|
||||
"tokens": "令牌",
|
||||
"type": "應用類型",
|
||||
"type-native": "原生應用",
|
||||
"type-web": "網絡應用",
|
||||
"unknown": "(未知)",
|
||||
"user-info-crypto-algorithm": "用戶資訊端點加密算法",
|
||||
"user-info-crypto-method": "用戶資訊端點加密方法",
|
||||
"user-info-signing-algorithm": "用戶資訊端點簽名算法"
|
||||
},
|
||||
"client-table": {
|
||||
"allow-introspection-tooltip": "這個客戶端可以執行令牌自省",
|
||||
"confirm": "你確定要刪除這個客戶端?",
|
||||
"dynamically-registered-tooltip": "這個客戶端是動態註冊的。點擊查看註冊訪問令牌",
|
||||
"match": {
|
||||
"contacts": "聯繫人",
|
||||
"description": "描述",
|
||||
"homepage": "主頁",
|
||||
"id": "身分",
|
||||
"logo": "標誌",
|
||||
"name": "名稱",
|
||||
"policy": "政策",
|
||||
"redirect": "重定向URI",
|
||||
"scope": "範圍",
|
||||
"terms": "服務條款"
|
||||
},
|
||||
"matched-search": "匹配搜索:",
|
||||
"new": "新客戶端",
|
||||
"no-clients": "此伺服器上沒有註冊的客戶端。",
|
||||
"no-matches": "沒有匹配搜索條件的客戶端。",
|
||||
"no-redirect": "沒有重定向URI",
|
||||
"registered": "註冊於",
|
||||
"search": "搜索……",
|
||||
"whitelist": "白名單",
|
||||
"unknown": "一個未知的時間"
|
||||
},
|
||||
"manage": "管理客戶端",
|
||||
"more-info": {
|
||||
"contacts": "管理員聯繫方式:",
|
||||
"home": "主頁",
|
||||
"more": "更多資訊",
|
||||
"policy": "政策",
|
||||
"terms": "服務條款:"
|
||||
},
|
||||
"newClient": "新客戶端"
|
||||
},
|
||||
"common": {
|
||||
"cancel": "取消",
|
||||
"client": "客戶端",
|
||||
"clients": "客戶端",
|
||||
"close": "關閉",
|
||||
"delete": "刪除",
|
||||
"description": "描述",
|
||||
"dynamically-registered": "這個客戶端是動態註冊的",
|
||||
"edit": "編輯",
|
||||
"expires": "到期:",
|
||||
"information": "資訊",
|
||||
"new": "新建",
|
||||
"not-yet-implemented": "未實現",
|
||||
"not-yet-implemented-content": "這個字段的值將於客戶端保存,但伺服器目前不處理任何事情。伺服器的未來庫版本將利用它。",
|
||||
"revoke": "撤銷",
|
||||
"save": "保存",
|
||||
"scopes": "範圍",
|
||||
"statistics": "統計",
|
||||
"refresh": "刷新",
|
||||
"scope": "範圍",
|
||||
"users": "用戶",
|
||||
"user": "用戶",
|
||||
"roles": "角色",
|
||||
"role": "角色",
|
||||
"email": "電子郵箱",
|
||||
"active": "已激活",
|
||||
"inactive": "未激活"
|
||||
},
|
||||
"dynreg": {
|
||||
"client-id-placeholder": "輸入客戶端ID",
|
||||
"configuration-url": "客戶端配置URL",
|
||||
"edit-dynamically-registered": "編輯動態註冊的客戶端",
|
||||
"edit-existing": "編輯一個現有的客戶端",
|
||||
"edit-existing-help": "用於編輯之前已註冊的客戶端。粘貼您的客戶端ID和註冊訪問令牌,以便訪問該客戶端。",
|
||||
"edit-existing-button": "編輯客戶端",
|
||||
"invalid-access-token": "無效的客戶端或註冊訪問令牌。",
|
||||
"new-client": "註冊新客戶端",
|
||||
"new-client-help": "用於註冊新的客戶端。請提供客戶端ID和註冊訪問令牌,以便管理您的客戶端。",
|
||||
"new-client-button": "新建客戶端",
|
||||
"regtoken-placeholder": "輸入註冊訪問令牌",
|
||||
"warning": "<strong>警告!</strong>你必須保護好<b>客戶端ID </b>,<b>客戶密鑰(如果提供)</b>,以及您的<b>註冊訪問令牌</b>。如果你丟失了客戶端ID或註冊訪問令牌,將無法訪問您的客戶端註冊記錄,你需要註冊一個新客戶端。",
|
||||
"will-be-generated": "當存儲客戶端資訊時將由伺服器自動生成"
|
||||
},
|
||||
"grant": {
|
||||
"manage-approved-sites": "管理批准的網站",
|
||||
"refresh": "刷新",
|
||||
"grant-table": {
|
||||
"active-tokens": "當前活躍的訪問令牌數量",
|
||||
"application": "應用程式",
|
||||
"approved-sites": "許可站點",
|
||||
"authorized": "授權:",
|
||||
"dynamically-registered": "這個客戶端是動態註冊的",
|
||||
"expires": "到期:",
|
||||
"last-accessed": "上次訪問:",
|
||||
"never": "從未",
|
||||
"no-sites": "還未批准任何網站。",
|
||||
"no-whitelisted": "還未訪問任何白名單的網站。",
|
||||
"pre-approved": "這些都是預先由管理員批准的網站。",
|
||||
"text": "這些都是您已經手動批准的網站。如果同一網站將來要進行同樣的訪問,它將直接通過、且沒有提示。",
|
||||
"unknown": "未知",
|
||||
"whitelist-note": "<b>註:</b>如果你在此撤銷它們,它們將在您下次訪問時不經提示即被自動重新批准。",
|
||||
"whitelisted-site": "這個網站由管理員列入白名單中",
|
||||
"whitelisted-sites": "白名單的網站"
|
||||
}
|
||||
},
|
||||
"rsreg": {
|
||||
"resource-id-placeholder": "輸入資源ID",
|
||||
"configuration-url": "客戶端配置URL",
|
||||
"edit": "編輯受保護的資源",
|
||||
"edit-existing": "編輯現有的保護資源",
|
||||
"edit-existing-help": "用於編輯之前已註冊的資源。請使用您的客戶端ID和註冊訪問令牌來訪問資源的屬性。",
|
||||
"edit-existing-button": "編輯資源",
|
||||
"invalid-access-token": "無效的客戶端或註冊訪問令牌。",
|
||||
"new-client": "註冊新的受保護資源",
|
||||
"new-client-help": "用於註冊新的資源。請提供客戶端ID和註冊訪問令牌,以便管理您的資源。",
|
||||
"new-client-button": "新建資源",
|
||||
"regtoken-placeholder": "輸入註冊訪問令牌",
|
||||
"will-be-generated": "將生成",
|
||||
"warning": "<strong>警告!</strong>你必須保護好<b>客戶端ID </b>,<b>客戶密鑰(如果提供)</b>,以及<b>註冊訪問令牌</b>。如果丟失了客戶端ID或註冊訪問令牌,將無法獲得您客戶端的登記記錄,你需要註冊一個新客戶端。",
|
||||
"client-form": {
|
||||
"scope-help": "這個資源能夠自省令牌的範圍。"
|
||||
}
|
||||
},
|
||||
"scope": {
|
||||
"manage": "管理系統範圍",
|
||||
"scope-list": {
|
||||
"no-scopes": "沒有範圍"
|
||||
},
|
||||
"system-scope-form": {
|
||||
"default": "默認範圍",
|
||||
"default-help": "新創建的用戶默認情況下獲得這個範圍?",
|
||||
"description-help": "人類可讀的文本描述",
|
||||
"description-placeholder": "輸入說明",
|
||||
"restricted": "限制",
|
||||
"restricted-help": "限制範圍衹能由系統管理員使用,可用動態註冊客戶和保護資源",
|
||||
"edit": "編輯範圍",
|
||||
"icon": "圖標",
|
||||
"new": "新範圍",
|
||||
"select-icon": "選擇圖標",
|
||||
"structured": "是一個結構化的範圍",
|
||||
"structured-help": "範圍結構化是否包含如<code>base:extension</code>的結構化值?",
|
||||
"structured-param-help": "人類可讀的結構化參數描述",
|
||||
"subject-type": "主體類型",
|
||||
"value": "範圍值",
|
||||
"value-help": "不含空格的單個字符串",
|
||||
"value-placeholder": "範圍"
|
||||
},
|
||||
"system-scope-table": {
|
||||
"confirm": "你確定要刪除此範圍?引用了此範圍的客戶端還需要它。",
|
||||
"new": "新範圍",
|
||||
"text": "尚未定義系統範圍。客戶還可自定義範圍。",
|
||||
"tooltip-restricted": "此範圍衹能由管理員使用。它不能用於動態註冊。",
|
||||
"tooltip-default": "這個範圍將自動分配給新註冊的客戶。"
|
||||
}
|
||||
},
|
||||
"token": {
|
||||
"manage": "管理活動的令牌",
|
||||
"token-table": {
|
||||
"access-tokens": "訪問令牌",
|
||||
"associated-id": "這個訪問令牌附帶相關的身份令牌。",
|
||||
"associated-refresh": "這個訪問令牌附帶相關的刷新令牌。",
|
||||
"click-to-display": "點擊顯示完整的令牌值",
|
||||
"confirm": "你確定要撤銷這個令牌?",
|
||||
"confirm-refresh": "你確定要撤銷這個刷新令牌及其相關的訪問令牌?",
|
||||
"expires": "過期",
|
||||
"no-access": "沒有活動的訪問令牌。",
|
||||
"no-refresh": "沒有活動的刷新令牌。",
|
||||
"number-of-tokens": "關聯的訪問令牌數量",
|
||||
"refresh-tokens": "刷新令牌",
|
||||
"text": "訪問令牌通常是短暫的,供客戶端訪問特定的資源。身份令牌是採用OpenID Connect協議登入的、專門的訪問令牌。",
|
||||
"text-refresh": "刷新令牌通常是長期的,以便客戶端能無需用戶介入即可獲取新的訪問令牌。",
|
||||
"token-info": "令牌的資訊"
|
||||
}
|
||||
},
|
||||
"whitelist": {
|
||||
"confirm": "你確定要刪除這個白名單項?",
|
||||
"edit": "編輯白名單",
|
||||
"manage": "管理列入白名單的網站",
|
||||
"new": "新白名單",
|
||||
"whitelist": "白名單",
|
||||
"whitelist-form": {
|
||||
"allowed-scopes": "允許範圍",
|
||||
"edit": "編輯白名單的網站",
|
||||
"new": "新增白名單網站",
|
||||
"scope-help": "當客戶端發出請求列表時將自動批准的範圍",
|
||||
"scope-placeholder": "新範圍"
|
||||
},
|
||||
"whitelist-table": {
|
||||
"no-sites": "白名單列表為空。使用<strong>白名單</strong>按鈕在客戶端管理頁面創建一個。"
|
||||
}
|
||||
},
|
||||
"blacklist": {
|
||||
"text": "被拉黑的網站URI將無法用於註冊客戶端的重定向地址(無論是在管理介面中添加、還是動態註冊,都不行)。",
|
||||
"blacklist-uri-placeholder": "要拉黑的網站URI",
|
||||
"add": "將網站URI加入黑名單",
|
||||
"empty": "當前黑名單為空",
|
||||
"uri": "URI"
|
||||
},
|
||||
"copyright": "基於<a href=\"https://github.com/mitreid-connect/\">MITREid Connect <span class=\"label\">{0}</span></a>技術構建 <span class=\"pull-right\">© 2016 MITRE公司及MIT因特網信任聯盟</span>。",
|
||||
"about": {
|
||||
"title": "關於",
|
||||
"body": "\n此OpenID Connect服務基於開源的MITREid Connect專案,該專案來自 \n<a href=\"http://www.mitre.org/\">MITRE公司</a> 及 <a href=\"http://kit.mit.edu/\">MIT因特網信任聯盟</a>。\n</p>\n<p>\n有關該專案的更多資訊可見 \n<a href=\"http://github.com/mitreid-connect/\">GitHub上的MITREid Connect專案</a>。 \n您可以在該網站報告bug、提交意見及代碼補丁。"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "統計",
|
||||
"number_users": "用戶數: <span class=\"label label-info\" id=\"userCount\">{0}</span>",
|
||||
"number_clients": "授權的客戶端: <span class=\"label label-info\" id=\"clientCount\">{0}</span>",
|
||||
"number_approvals": "已批准的站點: <span class=\"label label-info\" id=\"approvalCount\">{0}</span>"
|
||||
},
|
||||
"home": {
|
||||
"title": "首頁",
|
||||
"welcome": {
|
||||
"title": "歡迎!",
|
||||
"body": "\nOpenID Connect是適於因特網部署的身分聯邦認證伺服器,基於OAuth2授權框架之上的OpenID Connect技術構建。\nOpenID Connect讓您無需暴露自己的用戶名、密碼即可便捷登入網站。</p>\n<p><a class=\"btn btn-primary btn-large\" href=\"http://openid.net/connect/\">在此瞭解更多詳情»</a>"
|
||||
},
|
||||
"more": "更多",
|
||||
"about": {
|
||||
"title": "關於",
|
||||
"body": "本服務基於開源的MITREid Connect專案,該專案來自 \n<a href=\"http://www.mitre.org/\">MITRE公司</a> 及 <a href=\"http://kit.mit.edu/\">MIT因特網信任聯盟</a>。"
|
||||
},
|
||||
"contact": {
|
||||
"title": "聯繫方式",
|
||||
"body": "\n如需更多的資訊和支持,請聯繫本系統的管理員。</p>\n<p><a class=\"btn\" href=\"mailto:idp@example.com?Subject=OpenID Connect\">電子信箱 »</a>"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "當前統計",
|
||||
"loading": "加載……",
|
||||
"number_users": "用戶數: <span class=\"label label-info\" id=\"userCount\">{0}</span>",
|
||||
"number_clients": "授權的客戶端: <span class=\"label label-info\" id=\"clientCount\">{0}</span>",
|
||||
"number_approvals": "已批准的站點: <span class=\"label label-info\" id=\"approvalCount\">{0}</span>"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"title": "聯繫方式",
|
||||
"body": "如果要報告有關MITREid Connect軟體自身的bug,請拜訪\n<a href=\"https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/issues\">GitHub issue追蹤系統</a>。 \n有關當前伺服器的問題,請聯繫伺服器的管理者。"
|
||||
},
|
||||
"topbar": {
|
||||
"about": "關於",
|
||||
"contact": "聯繫方式",
|
||||
"statistics": "統計",
|
||||
"home": "首頁",
|
||||
"login": "登入",
|
||||
"logout": "登出"
|
||||
},
|
||||
"sidebar": {
|
||||
"administrative": {
|
||||
"title": "管理",
|
||||
"manage_clients": "管理客戶端",
|
||||
"whitelisted_clients": "白名單",
|
||||
"blacklisted_clients": "黑名單",
|
||||
"system_scopes": "系統範圍"
|
||||
},
|
||||
"personal": {
|
||||
"title": "個人",
|
||||
"approved_sites": "管理批准的網站",
|
||||
"active_tokens": "管理活動的令牌",
|
||||
"profile_information": "查看用戶資訊"
|
||||
},
|
||||
"developer": {
|
||||
"title": "開發者自助服務",
|
||||
"client_registration": "客戶端註冊",
|
||||
"resource_registration": "保護資源註冊"
|
||||
}
|
||||
},
|
||||
"manage": {
|
||||
"ok": "好的",
|
||||
"loading": "加載",
|
||||
"title": "管理控制檯"
|
||||
},
|
||||
"approve": {
|
||||
"dynamically-registered-unknown": "在一個未知的時間",
|
||||
"title": "批准訪問",
|
||||
"error": {
|
||||
"not_granted": "訪問可能不獲批准。"
|
||||
},
|
||||
"required_for": "有待批准",
|
||||
"dynamically_registered": "此客戶端已被動態註冊了<span class=\"label label-info\" id=\"registrationTime\">{0}</span>次。",
|
||||
"caution": {
|
||||
"title": "注意",
|
||||
"message": {
|
||||
"none": "它之前<span class=\"label label-important\">從未</span>被批准。",
|
||||
"singular": "它之前已被批准了<span class=\"label label-warning\">{0}</span>次。",
|
||||
"plural": "它之前已被批准了<span class=\"label\">{0}</span>次。"
|
||||
}
|
||||
},
|
||||
"more_information": "更多資訊",
|
||||
"home_page": "主頁",
|
||||
"policy": "政策",
|
||||
"terms": "服務條款",
|
||||
"contacts": "管理人員",
|
||||
"warning": "警告",
|
||||
"no_redirect_uri": "該客戶端沒有註冊任何重定向URI,可能被使用惡意的URI。",
|
||||
"redirect_uri": "如果點擊批准,您將被重定向至如下頁面: <code>{0}</code>",
|
||||
"pairwise": "該客戶端使用<b>pairwise</b>標識符,這使得在不同站點間關聯身份變得稍加困難。",
|
||||
"no_scopes": "該客戶端沒有註冊任何範圍,因此允許請求系統可用的<em>any</em>(任意)範圍。請務必謹慎處理。",
|
||||
"access_to": "訪問",
|
||||
"remember": {
|
||||
"title": "記住這個決定",
|
||||
"until_revoke": "記住這個決定直到我撤銷",
|
||||
"one_hour": "記住該決定一個小時",
|
||||
"next_time": "下次再提醒我"
|
||||
},
|
||||
"do_authorize": "是否授權",
|
||||
"label": {
|
||||
"authorize": "授權",
|
||||
"deny": "拒絕"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"title": "錯誤",
|
||||
"header": "錯誤:",
|
||||
"message": "在處理您的請求過程中發生了錯誤。伺服器日誌為:"
|
||||
},
|
||||
"login": {
|
||||
"login_with_username_and_password": "請用您的用戶名和密碼登入",
|
||||
"username": "用戶名",
|
||||
"password": "密碼",
|
||||
"login-button": "登入",
|
||||
"error": "登入失敗,請重試。"
|
||||
}
|
||||
}
|
|
@ -75,7 +75,7 @@ var ResRegRootView = Backbone.View.extend({
|
|||
$('#loadingbox').sheet('show');
|
||||
$('#loading').html('<span class="label" id="loading-scopes">' + $.t('common.scopes') + '</span> ');
|
||||
|
||||
$.when(this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}}))
|
||||
$.when(this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}, error:app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
$('#loadingbox').sheet('hide');
|
||||
callback();
|
||||
|
@ -106,37 +106,31 @@ var ResRegRootView = Backbone.View.extend({
|
|||
|
||||
var self = this;
|
||||
|
||||
client.fetch({success: function() {
|
||||
client.fetch({
|
||||
success: function() {
|
||||
|
||||
if (client.get("jwks")) {
|
||||
client.set({
|
||||
jwksType: "VAL"
|
||||
}, { silent: true });
|
||||
} else {
|
||||
client.set({
|
||||
jwksType: "URI"
|
||||
}, { silent: true });
|
||||
}
|
||||
|
||||
var view = new ResRegEditView({model: client, systemScopeList: app.systemScopeList});
|
||||
|
||||
view.load(function() {
|
||||
$('#content').html(view.render().el);
|
||||
view.delegateEvents();
|
||||
setPageTitle($.t('rsreg.new'));
|
||||
app.navigate('dev/resource/edit', {trigger: true});
|
||||
self.remove();
|
||||
});
|
||||
}, error: function() {
|
||||
$('#modalAlert div.modal-body').html("Invalid resource or registration access token.");
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
|
||||
}});
|
||||
if (client.get("jwks")) {
|
||||
client.set({
|
||||
jwksType: "VAL"
|
||||
}, { silent: true });
|
||||
} else {
|
||||
client.set({
|
||||
jwksType: "URI"
|
||||
}, { silent: true });
|
||||
}
|
||||
|
||||
var view = new ResRegEditView({model: client, systemScopeList: app.systemScopeList});
|
||||
|
||||
view.load(function() {
|
||||
$('#content').html(view.render().el);
|
||||
view.delegateEvents();
|
||||
setPageTitle($.t('rsreg.new'));
|
||||
app.navigate('dev/resource/edit', {trigger: true});
|
||||
self.remove();
|
||||
});
|
||||
},
|
||||
error:app.errorHandlerView.handleError({message: $.t('dynreg.invalid-access-token')})
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -169,7 +163,7 @@ var ResRegEditView = Backbone.View.extend({
|
|||
$('#loadingbox').sheet('show');
|
||||
$('#loading').html('<span class="label" id="loading-scopes">' + $.t('common.scopes') + '</span> ');
|
||||
|
||||
$.when(this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}}))
|
||||
$.when(this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}, error:app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
$('#loadingbox').sheet('hide');
|
||||
callback();
|
||||
|
@ -202,22 +196,7 @@ var ResRegEditView = Backbone.View.extend({
|
|||
self.remove();
|
||||
app.navigate('dev/resource', {trigger: true});
|
||||
},
|
||||
error:function (error, response) {
|
||||
console.log("An error occurred when deleting a client");
|
||||
|
||||
//Pull out the response text.
|
||||
var responseJson = JSON.parse(response.responseText);
|
||||
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html(responseJson.error);
|
||||
$('#modalAlert div.modal-body').html(responseJson.error_description);
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
}
|
||||
error:app.errorHandlerView.handleError()
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -332,16 +311,8 @@ var ResRegEditView = Backbone.View.extend({
|
|||
console.log("An error occurred when parsing the JWK Set");
|
||||
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html("JWK Set Error");
|
||||
$('#modalAlert div.modal-body').html("There was an error parsing the public key from the JSON Web Key set. Check the value and try again.");
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
|
||||
return false;
|
||||
app.errorHandlerView.showErrorMessage($.t("client.client-form.error.jwk-set"), $.t("client.client-form.error.jwk-set-parse"));
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
jwksUri = null;
|
||||
|
@ -396,22 +367,7 @@ var ResRegEditView = Backbone.View.extend({
|
|||
view.delegateEvents();
|
||||
});
|
||||
},
|
||||
error:function (error, response) {
|
||||
console.log("An error occurred when deleting from a list widget");
|
||||
|
||||
//Pull out the response text.
|
||||
var responseJson = JSON.parse(response.responseText);
|
||||
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html(responseJson.error);
|
||||
$('#modalAlert div.modal-body').html(responseJson.error_description);
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
}
|
||||
error:app.errorHandlerView.handleError()
|
||||
});
|
||||
|
||||
return false;
|
||||
|
|
|
@ -123,21 +123,7 @@ var SystemScopeView = Backbone.View.extend({
|
|||
});
|
||||
});
|
||||
},
|
||||
error:function (error, response) {
|
||||
|
||||
//Pull out the response text.
|
||||
var responseJson = JSON.parse(response.responseText);
|
||||
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html(responseJson.error);
|
||||
$('#modalAlert div.modal-body').html(responseJson.error_description);
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
}
|
||||
error:app.errorHandlerView.handleError()
|
||||
});
|
||||
|
||||
_self.parentView.delegateEvents();
|
||||
|
@ -170,7 +156,7 @@ var SystemScopeListView = Backbone.View.extend({
|
|||
'<span class="label" id="loading-scopes">' + $.t('common.scopes') + '</span> '
|
||||
);
|
||||
|
||||
$.when(this.model.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}}))
|
||||
$.when(this.model.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}, error:app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
$('#loadingbox').sheet('hide');
|
||||
callback();
|
||||
|
@ -194,7 +180,7 @@ var SystemScopeListView = Backbone.View.extend({
|
|||
'<span class="label" id="loading-scopes">' + $.t('common.scopes') + '</span> '
|
||||
);
|
||||
|
||||
$.when(this.model.fetch({success:function(e) {$('#loading-scopes').addClass('label-success');}}))
|
||||
$.when(this.model.fetch({success:function(e) {$('#loading-scopes').addClass('label-success');}, error:app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
$('#loadingbox').sheet('hide');
|
||||
_self.render();
|
||||
|
@ -296,7 +282,7 @@ var SystemScopeFormView = Backbone.View.extend({
|
|||
'<span class="label" id="loading-scopes">' + $.t("common.scopes") + '</span> '
|
||||
);
|
||||
|
||||
$.when(this.model.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}}))
|
||||
$.when(this.model.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}, error:app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
$('#loadingbox').sheet('hide');
|
||||
callback();
|
||||
|
@ -340,21 +326,7 @@ var SystemScopeFormView = Backbone.View.extend({
|
|||
app.systemScopeList.add(_self.model);
|
||||
app.navigate('admin/scope', {trigger: true});
|
||||
},
|
||||
error:function(error, response) {
|
||||
|
||||
//Pull out the response text.
|
||||
var responseJson = JSON.parse(response.responseText);
|
||||
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html(responseJson.error);
|
||||
$('#modalAlert div.modal-body').html(responseJson.error_description);
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
}
|
||||
error:app.errorHandlerView.handleError()
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -117,21 +117,7 @@ var AccessTokenView = Backbone.View.extend({
|
|||
});
|
||||
});
|
||||
},
|
||||
error:function (error, response) {
|
||||
|
||||
//Pull out the response text.
|
||||
var responseJson = JSON.parse(response.responseText);
|
||||
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html(responseJson.error);
|
||||
$('#modalAlert div.modal-body').html(responseJson.error_description);
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
}
|
||||
error:app.errorHandlerView.handleError()
|
||||
});
|
||||
|
||||
this.parentView.delegateEvents();
|
||||
|
@ -269,21 +255,7 @@ var RefreshTokenView = Backbone.View.extend({
|
|||
});
|
||||
});
|
||||
},
|
||||
error:function (error, response) {
|
||||
|
||||
//Pull out the response text.
|
||||
var responseJson = JSON.parse(response.responseText);
|
||||
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html(responseJson.error);
|
||||
$('#modalAlert div.modal-body').html(responseJson.error_description);
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
}
|
||||
error:app.errorHandlerView.handleError()
|
||||
});
|
||||
|
||||
_self.parentView.delegateEvents();
|
||||
|
@ -350,10 +322,10 @@ var TokenListView = Backbone.View.extend({
|
|||
'<span class="label" id="loading-scopes">' + $.t('common.scopes') + '</span> '
|
||||
);
|
||||
|
||||
$.when(this.model.access.fetchIfNeeded({success:function(e) {$('#loading-access').addClass('label-success');}}),
|
||||
this.model.refresh.fetchIfNeeded({success:function(e) {$('#loading-refresh').addClass('label-success');}}),
|
||||
this.options.clientList.fetchIfNeeded({success:function(e) {$('#loading-clients').addClass('label-success');}}),
|
||||
this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}}))
|
||||
$.when(this.model.access.fetchIfNeeded({success:function(e) {$('#loading-access').addClass('label-success');}, error: app.errorHandlerView.handleError()}),
|
||||
this.model.refresh.fetchIfNeeded({success:function(e) {$('#loading-refresh').addClass('label-success');}, error: app.errorHandlerView.handleError()}),
|
||||
this.options.clientList.fetchIfNeeded({success:function(e) {$('#loading-clients').addClass('label-success');}, error: app.errorHandlerView.handleError()}),
|
||||
this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}, error: app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
$('#loadingbox').sheet('hide');
|
||||
callback();
|
||||
|
@ -392,10 +364,10 @@ var TokenListView = Backbone.View.extend({
|
|||
'<span class="label" id="loading-scopes">' + $.t('common.scopes') + '</span> '
|
||||
);
|
||||
var _self = this;
|
||||
$.when(this.model.access.fetch({success:function(e) {$('#loading-access').addClass('label-success');}}),
|
||||
this.model.refresh.fetch({success:function(e) {$('#loading-refresh').addClass('label-success');}}),
|
||||
this.options.clientList.fetch({success:function(e) {$('#loading-clients').addClass('label-success');}}),
|
||||
this.options.systemScopeList.fetch({success:function(e) {$('#loading-scopes').addClass('label-success');}}))
|
||||
$.when(this.model.access.fetch({success:function(e) {$('#loading-access').addClass('label-success');}, error: app.errorHandlerView.handleError()}),
|
||||
this.model.refresh.fetch({success:function(e) {$('#loading-refresh').addClass('label-success');}, error: app.errorHandlerView.handleError()}),
|
||||
this.options.clientList.fetch({success:function(e) {$('#loading-clients').addClass('label-success');}, error: app.errorHandlerView.handleError()}),
|
||||
this.options.systemScopeList.fetch({success:function(e) {$('#loading-scopes').addClass('label-success');}, error: app.errorHandlerView.handleError()}))
|
||||
.done(function(){
|
||||
_self.render();
|
||||
$('#loadingbox').sheet('hide');
|
||||
|
|
|
@ -65,9 +65,9 @@ var WhiteListListView = Backbone.View.extend({
|
|||
'<span class="label" id="loading-scopes">' + $.t('common.scopes') + '</span> '
|
||||
);
|
||||
|
||||
$.when(this.model.fetchIfNeeded({success:function(e) {$('#loading-whitelist').addClass('label-success');}}),
|
||||
this.options.clientList.fetchIfNeeded({success:function(e) {$('#loading-clients').addClass('label-success');}}),
|
||||
this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}}))
|
||||
$.when(this.model.fetchIfNeeded({success:function(e) {$('#loading-whitelist').addClass('label-success');}, error: app.errorHandlerView.handleError()}),
|
||||
this.options.clientList.fetchIfNeeded({success:function(e) {$('#loading-clients').addClass('label-success');}, error: app.errorHandlerView.handleError()}),
|
||||
this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}, error: app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
$('#loadingbox').sheet('hide');
|
||||
callback();
|
||||
|
@ -122,9 +122,9 @@ var WhiteListListView = Backbone.View.extend({
|
|||
'<span class="label" id="loading-scopes">' + $.t('common.scopes') + '</span> '
|
||||
);
|
||||
|
||||
$.when(this.model.fetch({success:function(e) {$('#loading-whitelist').addClass('label-success');}}),
|
||||
this.options.clientList.fetch({success:function(e) {$('#loading-clients').addClass('label-success');}}),
|
||||
this.options.systemScopeList.fetch({success:function(e) {$('#loading-scopes').addClass('label-success');}}))
|
||||
$.when(this.model.fetch({success:function(e) {$('#loading-whitelist').addClass('label-success');}, error: app.errorHandlerView.handleError()}),
|
||||
this.options.clientList.fetch({success:function(e) {$('#loading-clients').addClass('label-success');}, error: app.errorHandlerView.handleError()}),
|
||||
this.options.systemScopeList.fetch({success:function(e) {$('#loading-scopes').addClass('label-success');}, error: app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
$('#loadingbox').sheet('hide');
|
||||
_self.render();
|
||||
|
@ -196,22 +196,7 @@ var WhiteListView = Backbone.View.extend({
|
|||
});
|
||||
});
|
||||
},
|
||||
error:function (error, response) {
|
||||
console.log("An error occurred when deleting a whitelist entry");
|
||||
|
||||
//Pull out the response text.
|
||||
var responseJson = JSON.parse(response.responseText);
|
||||
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html(responseJson.error);
|
||||
$('#modalAlert div.modal-body').html(responseJson.error_description);
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
}
|
||||
error:app.errorHandlerView.handleError()
|
||||
});
|
||||
|
||||
_self.parentView.delegateEvents();
|
||||
|
@ -274,9 +259,9 @@ var WhiteListFormView = Backbone.View.extend({
|
|||
'<span class="label" id="loading-scopes">' + $.t('common.scopes') + '</span> '
|
||||
);
|
||||
|
||||
$.when(this.model.fetchIfNeeded({success:function(e) {$('#loading-whitelist').addClass('label-success');}}),
|
||||
this.options.client.fetchIfNeeded({success:function(e) {$('#loading-clients').addClass('label-success');}}),
|
||||
this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}}))
|
||||
$.when(this.model.fetchIfNeeded({success:function(e) {$('#loading-whitelist').addClass('label-success');}, error: app.errorHandlerView.handleError()}),
|
||||
this.options.client.fetchIfNeeded({success:function(e) {$('#loading-clients').addClass('label-success');}, error: app.errorHandlerView.handleError()}),
|
||||
this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}, error: app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
$('#loadingbox').sheet('hide');
|
||||
callback();
|
||||
|
@ -305,9 +290,9 @@ var WhiteListFormView = Backbone.View.extend({
|
|||
|
||||
var _self = this;
|
||||
|
||||
$.when(this.model.fetchIfNeeded({success:function(e) {$('#loading-whitelist').addClass('label-success');}}),
|
||||
this.options.clientList.fetchIfNeeded({success:function(e) {$('#loading-clients').addClass('label-success');}}),
|
||||
this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}}))
|
||||
$.when(this.model.fetchIfNeeded({success:function(e) {$('#loading-whitelist').addClass('label-success');}, error: app.errorHandlerView.handleError()}),
|
||||
this.options.clientList.fetchIfNeeded({success:function(e) {$('#loading-clients').addClass('label-success');}, error: app.errorHandlerView.handleError()}),
|
||||
this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}, error: app.errorHandlerView.handleError()}))
|
||||
.done(function() {
|
||||
|
||||
var client = _self.options.clientList.getByClientId(_self.model.get('clientId'));
|
||||
|
@ -355,22 +340,7 @@ var WhiteListFormView = Backbone.View.extend({
|
|||
app.whiteListList.add(_self.model);
|
||||
app.navigate('admin/whitelists', {trigger:true});
|
||||
},
|
||||
error:function (error, response) {
|
||||
console.log("An error occurred when deleting from a list widget");
|
||||
|
||||
//Pull out the response text.
|
||||
var responseJson = JSON.parse(response.responseText);
|
||||
|
||||
//Display an alert with an error message
|
||||
$('#modalAlert div.modal-header').html(responseJson.error);
|
||||
$('#modalAlert div.modal-body').html(responseJson.error_description);
|
||||
|
||||
$("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
|
||||
"backdrop" : "static",
|
||||
"keyboard" : true,
|
||||
"show" : true // ensure the modal is shown immediately
|
||||
});
|
||||
}
|
||||
error:app.errorHandlerView.handleError()
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -110,3 +110,50 @@
|
|||
<dd><%- value %></dd>
|
||||
|
||||
</script>
|
||||
|
||||
<!-- error box -->
|
||||
|
||||
<script type="text/html" id="tmpl-error-box">
|
||||
|
||||
<p>
|
||||
<span data-i18n="error.message">There was an error processing your request.</span>
|
||||
|
||||
<% if (response.responseJSON) { %>
|
||||
<span data-i18n="error.server-message">The server said: </span><span class="text-error"><%- response.responseJSON.error_description %></span>
|
||||
<% } %>
|
||||
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
||||
<% if (message.message) { %>
|
||||
<span class="text-info"><%- message.message %></span>
|
||||
<% } %>
|
||||
|
||||
</p>
|
||||
|
||||
<% if (response.status == 401) { %>
|
||||
<div>
|
||||
<span data-i18n="error.reload">Additionally, it looks like you're not logged in. Reload the page to try again. You will likely lose any unsaved work.</span>
|
||||
<p>
|
||||
<button class="btn btn-inverse btn-mini page-reload"><i class="icon-refresh icon-white"></i> <span data-i18n="error.reload-button">Reload</span></button>
|
||||
</p>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
</script>
|
||||
|
||||
<script type="text/html" id="tmpl-error-header">
|
||||
|
||||
<span class="label label-inverse"><%- response.status %>: <%- response.statusText %></span>
|
||||
|
||||
<b>
|
||||
<% if (response.responseJSON) { %>
|
||||
<span data-i18n="error.header-with-message">Error: </span><%- response.responseJSON.error %>
|
||||
<% } else { %>
|
||||
<span data-i18n="error.header">Error</span>
|
||||
<% } %>
|
||||
</b>
|
||||
|
||||
|
||||
</script>
|
|
@ -298,6 +298,16 @@
|
|||
<div class="controls">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group" id="softwareStatement">
|
||||
<label class="control-label" data-i18n="client.client-form.software-statement">Software Statement</label>
|
||||
<div class="controls">
|
||||
<textarea class="input-xlarge" placeholder="ejy0..." maxlength="4096"
|
||||
rows="3" data-i18n="[placeholder]client.client-form.software-statement-placeholder"><%-client.softwareStatement%></textarea>
|
||||
<p class="help-block" data-i18n="client.client-form.software-statement-help">A software statement is issued by a trusted third party and locks certain elements of a client's registration</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="client-access-tab">
|
||||
|
|
|
@ -210,6 +210,15 @@
|
|||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group" id="softwareStatement">
|
||||
<label class="control-label" data-i18n="client.client-form.software-statement">Software Statement</label>
|
||||
<div class="controls">
|
||||
<textarea class="input-xlarge" placeholder="ejy0..." maxlength="4096"
|
||||
rows="3" data-i18n="[placeholder]client.client-form.software-statement-placeholder"><%-client.software_statement%></textarea>
|
||||
<p class="help-block" data-i18n="client.client-form.software-statement-help">A software statement is issued by a trusted third party and locks certain elements of a client's registration</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="client-access-tab">
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<parent>
|
||||
<groupId>org.mitre</groupId>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<version>1.2.7.cnaf-SNAPSHOT</version>
|
||||
<version>1.3.0.cnaf-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<build>
|
||||
|
@ -46,6 +46,10 @@
|
|||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-tx</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.persistence</groupId>
|
||||
<artifactId>org.eclipse.persistence.core</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
<description>OpenID Connect server libraries for Spring and Spring Security.</description>
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.util.Map;
|
|||
import org.mitre.discovery.util.WebfingerURLNormalizer;
|
||||
import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService;
|
||||
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
|
||||
import org.mitre.oauth2.model.PKCEAlgorithm;
|
||||
import org.mitre.oauth2.service.SystemScopeService;
|
||||
import org.mitre.oauth2.web.IntrospectionEndpoint;
|
||||
import org.mitre.oauth2.web.RevocationEndpoint;
|
||||
|
@ -364,6 +365,9 @@ public class DiscoveryEndpoint {
|
|||
m.put("introspection_endpoint", baseUrl + IntrospectionEndpoint.URL); // token introspection endpoint for verifying tokens
|
||||
m.put("revocation_endpoint", baseUrl + RevocationEndpoint.URL); // token revocation endpoint
|
||||
|
||||
m.put("code_challenge_methods_supported", Lists.newArrayList(PKCEAlgorithm.plain.getName(), PKCEAlgorithm.S256.getName()));
|
||||
|
||||
|
||||
model.addAttribute(JsonEntityView.ENTITY, m);
|
||||
|
||||
return JsonEntityView.VIEWNAME;
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2016 The MITRE Corporation
|
||||
* and the MIT Internet Trust Consortium
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*******************************************************************************/
|
||||
|
||||
package org.mitre.oauth2.assertion;
|
||||
|
||||
import org.springframework.security.oauth2.provider.ClientDetails;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Request;
|
||||
import org.springframework.security.oauth2.provider.TokenRequest;
|
||||
|
||||
import com.nimbusds.jwt.JWT;
|
||||
|
||||
/**
|
||||
* Take in an assertion and token request and generate an OAuth2Request from it, including scopes and other important components
|
||||
*
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
public interface AssertionOAuth2RequestFactory {
|
||||
|
||||
/**
|
||||
* @param client
|
||||
* @param tokenRequest
|
||||
* @param assertion
|
||||
* @return
|
||||
*/
|
||||
OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest, JWT assertion);
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2016 The MITRE Corporation
|
||||
* and the MIT Internet Trust Consortium
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*******************************************************************************/
|
||||
|
||||
package org.mitre.oauth2.assertion.impl;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mitre.oauth2.assertion.AssertionOAuth2RequestFactory;
|
||||
import org.springframework.security.oauth2.common.util.OAuth2Utils;
|
||||
import org.springframework.security.oauth2.provider.ClientDetails;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Request;
|
||||
import org.springframework.security.oauth2.provider.TokenRequest;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import com.nimbusds.jwt.JWT;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
|
||||
/**
|
||||
* Takes an assertion from a trusted source, looks for the fields:
|
||||
*
|
||||
* - scope, space-separated list of strings
|
||||
* - aud, array of audience IDs
|
||||
*
|
||||
* @author jricher
|
||||
*
|
||||
*/
|
||||
public class DirectCopyRequestFactory implements AssertionOAuth2RequestFactory {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.mitre.oauth2.assertion.AssertionOAuth2RequestFactory#createOAuth2Request(org.springframework.security.oauth2.provider.ClientDetails, org.springframework.security.oauth2.provider.TokenRequest, com.nimbusds.jwt.JWT)
|
||||
*/
|
||||
@Override
|
||||
public OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest, JWT assertion) {
|
||||
|
||||
try {
|
||||
JWTClaimsSet claims = assertion.getJWTClaimsSet();
|
||||
Set<String> scope = OAuth2Utils.parseParameterList(claims.getStringClaim("scope"));
|
||||
|
||||
Set<String> resources = Sets.newHashSet(claims.getAudience());
|
||||
|
||||
return new OAuth2Request(tokenRequest.getRequestParameters(), client.getClientId(), client.getAuthorities(), true, scope, resources, null, null, null);
|
||||
} catch (ParseException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -19,6 +19,13 @@
|
|||
*/
|
||||
package org.mitre.oauth2.service.impl;
|
||||
|
||||
import static org.mitre.openid.connect.request.ConnectRequestParameters.CODE_CHALLENGE;
|
||||
import static org.mitre.openid.connect.request.ConnectRequestParameters.CODE_CHALLENGE_METHOD;
|
||||
import static org.mitre.openid.connect.request.ConnectRequestParameters.CODE_VERIFIER;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
|
@ -30,6 +37,7 @@ import org.mitre.oauth2.model.AuthenticationHolderEntity;
|
|||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
|
||||
import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
|
||||
import org.mitre.oauth2.model.PKCEAlgorithm;
|
||||
import org.mitre.oauth2.model.SystemScope;
|
||||
import org.mitre.oauth2.repository.AuthenticationHolderRepository;
|
||||
import org.mitre.oauth2.repository.OAuth2TokenRepository;
|
||||
|
@ -44,9 +52,9 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidRequestException;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
|
||||
import org.springframework.security.oauth2.provider.ClientAlreadyExistsException;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Request;
|
||||
import org.springframework.security.oauth2.provider.TokenRequest;
|
||||
|
@ -54,6 +62,7 @@ import org.springframework.security.oauth2.provider.token.TokenEnhancer;
|
|||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import com.nimbusds.jose.util.Base64URL;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.PlainJWT;
|
||||
|
||||
|
@ -169,14 +178,43 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
|
|||
public OAuth2AccessTokenEntity createAccessToken(OAuth2Authentication authentication) throws AuthenticationException, InvalidClientException {
|
||||
if (authentication != null && authentication.getOAuth2Request() != null) {
|
||||
// look up our client
|
||||
OAuth2Request clientAuth = authentication.getOAuth2Request();
|
||||
OAuth2Request request = authentication.getOAuth2Request();
|
||||
|
||||
ClientDetailsEntity client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
|
||||
ClientDetailsEntity client = clientDetailsService.loadClientByClientId(request.getClientId());
|
||||
|
||||
if (client == null) {
|
||||
throw new InvalidClientException("Client not found: " + clientAuth.getClientId());
|
||||
throw new InvalidClientException("Client not found: " + request.getClientId());
|
||||
}
|
||||
|
||||
|
||||
// handle the PKCE code challenge if present
|
||||
if (request.getExtensions().containsKey(CODE_CHALLENGE)) {
|
||||
String challenge = (String) request.getExtensions().get(CODE_CHALLENGE);
|
||||
PKCEAlgorithm alg = PKCEAlgorithm.parse((String) request.getExtensions().get(CODE_CHALLENGE_METHOD));
|
||||
|
||||
String verifier = request.getRequestParameters().get(CODE_VERIFIER);
|
||||
|
||||
if (alg.equals(PKCEAlgorithm.plain)) {
|
||||
// do a direct string comparison
|
||||
if (!challenge.equals(verifier)) {
|
||||
throw new InvalidRequestException("Code challenge and verifier do not match");
|
||||
}
|
||||
} else if (alg.equals(PKCEAlgorithm.S256)) {
|
||||
// hash the verifier
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
String hash = Base64URL.encode(digest.digest(verifier.getBytes(StandardCharsets.US_ASCII))).toString();
|
||||
if (!challenge.equals(hash)) {
|
||||
throw new InvalidRequestException("Code challenge and verifier do not match");
|
||||
}
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
logger.error("Unknown algorithm for PKCE digest", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity();//accessTokenFactory.createNewAccessToken();
|
||||
|
||||
// attach the client
|
||||
|
@ -185,7 +223,7 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
|
|||
// inherit the scope from the auth, but make a new set so it is
|
||||
//not unmodifiable. Unmodifiables don't play nicely with Eclipselink, which
|
||||
//wants to use the clone operation.
|
||||
Set<SystemScope> scopes = scopeService.fromStrings(clientAuth.getScope());
|
||||
Set<SystemScope> scopes = scopeService.fromStrings(request.getScope());
|
||||
|
||||
// remove any of the special system scopes
|
||||
scopes = scopeService.removeReservedScopes(scopes);
|
||||
|
|
|
@ -20,33 +20,27 @@
|
|||
package org.mitre.oauth2.token;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.mitre.jwt.assertion.AssertionValidator;
|
||||
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
|
||||
import org.mitre.oauth2.assertion.AssertionOAuth2RequestFactory;
|
||||
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
||||
import org.mitre.oauth2.service.OAuth2TokenEntityService;
|
||||
import org.mitre.oauth2.service.SystemScopeService;
|
||||
import org.mitre.openid.connect.assertion.JWTBearerAssertionAuthenticationToken;
|
||||
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
|
||||
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
|
||||
import org.springframework.security.oauth2.provider.ClientDetails;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
|
||||
import org.springframework.security.oauth2.provider.TokenRequest;
|
||||
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.nimbusds.jose.JWSHeader;
|
||||
import com.nimbusds.jwt.JWT;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.JWTParser;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
|
||||
/**
|
||||
* @author jricher
|
||||
|
@ -65,6 +59,13 @@ public class JWTAssertionTokenGranter extends AbstractTokenGranter {
|
|||
|
||||
@Autowired
|
||||
private ConfigurationPropertiesBean config;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("jwtAssertionValidator")
|
||||
private AssertionValidator validator;
|
||||
|
||||
@Autowired
|
||||
private AssertionOAuth2RequestFactory assertionFactory;
|
||||
|
||||
@Autowired
|
||||
public JWTAssertionTokenGranter(OAuth2TokenEntityService tokenServices, ClientDetailsEntityService clientDetailsService, OAuth2RequestFactory requestFactory) {
|
||||
|
@ -76,129 +77,31 @@ public class JWTAssertionTokenGranter extends AbstractTokenGranter {
|
|||
* @see org.springframework.security.oauth2.provider.token.AbstractTokenGranter#getOAuth2Authentication(org.springframework.security.oauth2.provider.AuthorizationRequest)
|
||||
*/
|
||||
@Override
|
||||
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) throws AuthenticationException, InvalidTokenException {
|
||||
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) throws AuthenticationException, InvalidTokenException {
|
||||
// read and load up the existing token
|
||||
String incomingTokenValue = tokenRequest.getRequestParameters().get("assertion");
|
||||
OAuth2AccessTokenEntity incomingToken = tokenServices.readAccessToken(incomingTokenValue);
|
||||
|
||||
if (incomingToken.getScope().contains(SystemScopeService.ID_TOKEN_SCOPE)) {
|
||||
|
||||
if (!client.getClientId().equals(tokenRequest.getClientId())) {
|
||||
throw new InvalidClientException("Not the right client for this token");
|
||||
try {
|
||||
String incomingAssertionValue = tokenRequest.getRequestParameters().get("assertion");
|
||||
JWT assertion = JWTParser.parse(incomingAssertionValue);
|
||||
|
||||
if (validator.isValid(assertion)) {
|
||||
|
||||
// our validator says it's OK, time to make a token from it
|
||||
// the real work happens in the assertion factory and the token services
|
||||
return new OAuth2Authentication(assertionFactory.createOAuth2Request(client, tokenRequest, assertion),
|
||||
new JWTBearerAssertionAuthenticationToken(assertion, client.getAuthorities()));
|
||||
|
||||
} else {
|
||||
logger.warn("Incoming assertion did not pass validator, rejecting");
|
||||
return null;
|
||||
}
|
||||
|
||||
// it's an ID token, process it accordingly
|
||||
|
||||
try {
|
||||
|
||||
// TODO: make this use a more specific idtoken class
|
||||
JWT idToken = JWTParser.parse(incomingTokenValue);
|
||||
|
||||
OAuth2AccessTokenEntity accessToken = tokenServices.getAccessTokenForIdToken(incomingToken);
|
||||
|
||||
if (accessToken != null) {
|
||||
|
||||
//OAuth2AccessTokenEntity newIdToken = tokenServices.get
|
||||
|
||||
OAuth2AccessTokenEntity newIdTokenEntity = new OAuth2AccessTokenEntity();
|
||||
|
||||
// copy over all existing claims
|
||||
JWTClaimsSet.Builder claims = new JWTClaimsSet.Builder(idToken.getJWTClaimsSet());
|
||||
|
||||
if (client instanceof ClientDetailsEntity) {
|
||||
|
||||
ClientDetailsEntity clientEntity = (ClientDetailsEntity) client;
|
||||
|
||||
// update expiration and issued-at claims
|
||||
if (clientEntity.getIdTokenValiditySeconds() != null) {
|
||||
Date expiration = new Date(System.currentTimeMillis() + (clientEntity.getIdTokenValiditySeconds() * 1000L));
|
||||
claims.expirationTime(expiration);
|
||||
newIdTokenEntity.setExpiration(expiration);
|
||||
}
|
||||
|
||||
} else {
|
||||
//This should never happen
|
||||
logger.fatal("SEVERE: Client is not an instance of OAuth2AccessTokenEntity.");
|
||||
throw new BadCredentialsException("SEVERE: Client is not an instance of ClientDetailsEntity; JwtAssertionTokenGranter cannot process this request.");
|
||||
}
|
||||
|
||||
claims.issueTime(new Date());
|
||||
claims.jwtID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it
|
||||
|
||||
|
||||
SignedJWT newIdToken = new SignedJWT((JWSHeader) idToken.getHeader(), claims.build());
|
||||
jwtService.signJwt(newIdToken);
|
||||
|
||||
newIdTokenEntity.setJwt(newIdToken);
|
||||
newIdTokenEntity.setAuthenticationHolder(incomingToken.getAuthenticationHolder());
|
||||
newIdTokenEntity.setScope(incomingToken.getScope());
|
||||
newIdTokenEntity.setClient(incomingToken.getClient());
|
||||
|
||||
newIdTokenEntity = tokenServices.saveAccessToken(newIdTokenEntity);
|
||||
|
||||
// attach the ID token to the access token entity
|
||||
accessToken.setIdToken(newIdTokenEntity);
|
||||
accessToken = tokenServices.saveAccessToken(accessToken);
|
||||
|
||||
// delete the old ID token
|
||||
tokenServices.revokeAccessToken(incomingToken);
|
||||
|
||||
return newIdTokenEntity;
|
||||
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
logger.warn("Couldn't parse id token", e);
|
||||
}
|
||||
|
||||
|
||||
} catch (ParseException e) {
|
||||
logger.warn("Unable to parse incoming assertion");
|
||||
}
|
||||
|
||||
// if we got down here, we didn't actually create any tokens, so return null
|
||||
|
||||
|
||||
// if we had made a token, we'd have returned it by now, so return null here to close out with no created token
|
||||
return null;
|
||||
|
||||
/*
|
||||
* Otherwise, process it like an access token assertion ... which we don't support yet so this is all commented out
|
||||
* /
|
||||
if (jwtService.validateSignature(incomingTokenValue)) {
|
||||
|
||||
Jwt jwt = Jwt.parse(incomingTokenValue);
|
||||
|
||||
|
||||
if (oldToken.getScope().contains("id-token")) {
|
||||
// TODO: things
|
||||
}
|
||||
|
||||
// TODO: should any of these throw an exception instead of returning null?
|
||||
JwtClaims claims = jwt.getClaims();
|
||||
if (!config.getIssuer().equals(claims.getIssuer())) {
|
||||
// issuer isn't us
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!authorizationRequest.getClientId().equals(claims.getAudience())) {
|
||||
// audience isn't the client
|
||||
return null;
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
if (!now.after(claims.getExpiration())) {
|
||||
// token is expired
|
||||
return null;
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// This doesn't work. We need to look up the old token, figure out its scopes and bind it appropriately.
|
||||
// In the case of an ID token, we need to look up its parent access token and change the reference, and revoke the old one, and
|
||||
// that's tricky.
|
||||
// we might need new calls on the token services layer to handle this, and we might
|
||||
// need to handle id tokens separately.
|
||||
return new OAuth2Authentication(authorizationRequest, null);
|
||||
|
||||
} else {
|
||||
return null; // throw error??
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
*/
|
||||
package org.mitre.openid.connect.assertion;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
|
@ -36,30 +37,42 @@ public class JWTBearerAssertionAuthenticationToken extends AbstractAuthenticatio
|
|||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -3138213539914074617L;
|
||||
private String clientId;
|
||||
private String subject;
|
||||
private JWT jwt;
|
||||
|
||||
/**
|
||||
* Create an unauthenticated token with the given client ID and jwt
|
||||
* @param clientId
|
||||
* Create an unauthenticated token with the given subject and jwt
|
||||
* @param subject
|
||||
* @param jwt
|
||||
*/
|
||||
public JWTBearerAssertionAuthenticationToken(String clientId, JWT jwt) {
|
||||
public JWTBearerAssertionAuthenticationToken(JWT jwt) {
|
||||
super(null);
|
||||
this.clientId = clientId;
|
||||
try {
|
||||
// save the subject of the JWT in case the credentials get erased later
|
||||
this.subject = jwt.getJWTClaimsSet().getSubject();
|
||||
} catch (ParseException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.jwt = jwt;
|
||||
setAuthenticated(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an authenticated token with the given clientID, jwt, and authorities set
|
||||
* @param clientId
|
||||
* @param subject
|
||||
* @param jwt
|
||||
* @param authorities
|
||||
*/
|
||||
public JWTBearerAssertionAuthenticationToken(String clientId, JWT jwt, Collection<? extends GrantedAuthority> authorities) {
|
||||
public JWTBearerAssertionAuthenticationToken(JWT jwt, Collection<? extends GrantedAuthority> authorities) {
|
||||
super(authorities);
|
||||
this.clientId = clientId;
|
||||
try {
|
||||
// save the subject of the JWT in case the credentials get erased later
|
||||
this.subject = jwt.getJWTClaimsSet().getSubject();
|
||||
} catch (ParseException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.jwt = jwt;
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
@ -77,21 +90,7 @@ public class JWTBearerAssertionAuthenticationToken extends AbstractAuthenticatio
|
|||
*/
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the clientId
|
||||
*/
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clientId the clientId to set
|
||||
*/
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
return subject;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -85,7 +85,7 @@ public class JWTBearerAuthenticationProvider implements AuthenticationProvider {
|
|||
|
||||
|
||||
try {
|
||||
ClientDetailsEntity client = clientService.loadClientByClientId(jwtAuth.getClientId());
|
||||
ClientDetailsEntity client = clientService.loadClientByClientId(jwtAuth.getName());
|
||||
|
||||
JWT jwt = jwtAuth.getJwt();
|
||||
JWTClaimsSet jwtClaims = jwt.getJWTClaimsSet();
|
||||
|
@ -191,10 +191,10 @@ public class JWTBearerAuthenticationProvider implements AuthenticationProvider {
|
|||
Set<GrantedAuthority> authorities = new HashSet<>(client.getAuthorities());
|
||||
authorities.add(ROLE_CLIENT);
|
||||
|
||||
return new JWTBearerAssertionAuthenticationToken(client.getClientId(), jwt, authorities);
|
||||
return new JWTBearerAssertionAuthenticationToken(jwt, authorities);
|
||||
|
||||
} catch (InvalidClientException e) {
|
||||
throw new UsernameNotFoundException("Could not find client: " + jwtAuth.getClientId());
|
||||
throw new UsernameNotFoundException("Could not find client: " + jwtAuth.getName());
|
||||
} catch (ParseException e) {
|
||||
|
||||
logger.error("Failure during authentication, error was: ", e);
|
||||
|
|
|
@ -95,7 +95,7 @@ public class JWTBearerClientAssertionTokenEndpointFilter extends AbstractAuthent
|
|||
|
||||
String clientId = jwt.getJWTClaimsSet().getSubject();
|
||||
|
||||
Authentication authRequest = new JWTBearerAssertionAuthenticationToken(clientId, jwt);
|
||||
Authentication authRequest = new JWTBearerAssertionAuthenticationToken(jwt);
|
||||
|
||||
return this.getAuthenticationManager().authenticate(authRequest);
|
||||
} catch (ParseException e) {
|
||||
|
|
|
@ -163,10 +163,18 @@ public class JsonMessageSource extends AbstractMessageSource {
|
|||
try {
|
||||
List<JsonObject> set = new ArrayList<>();
|
||||
for (String namespace : config.getLanguageNamespaces()) {
|
||||
String filename = locale.getLanguage() + File.separator + namespace + ".json";
|
||||
// full locale string, e.g. "en_US"
|
||||
String filename = locale.getLanguage() + "_" + locale.getCountry() + File.separator + namespace + ".json";
|
||||
|
||||
Resource r = getBaseDirectory().createRelative(filename);
|
||||
|
||||
if (!r.exists()) {
|
||||
// fallback to language only
|
||||
logger.debug("Fallback locale to language only.");
|
||||
filename = locale.getLanguage() + File.separator + namespace + ".json";
|
||||
r = getBaseDirectory().createRelative(filename);
|
||||
}
|
||||
|
||||
logger.info("No locale loaded, trying to load from " + r);
|
||||
|
||||
JsonParser parser = new JsonParser();
|
||||
|
|
|
@ -51,6 +51,8 @@ import org.springframework.security.oauth2.common.exceptions.InvalidClientExcept
|
|||
import org.springframework.security.oauth2.provider.AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
|
||||
import org.springframework.security.oauth2.provider.endpoint.RedirectResolver;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.GenericFilterBean;
|
||||
|
||||
|
@ -83,6 +85,8 @@ public class AuthorizationRequestFilter extends GenericFilterBean {
|
|||
|
||||
@Autowired(required = false)
|
||||
private LoginHintExtracter loginHintExtracter = new RemoveLoginHintsWithHTTP();
|
||||
|
||||
private RequestMatcher requestMatcher = new AntPathRequestMatcher("/authorize");
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -95,7 +99,7 @@ public class AuthorizationRequestFilter extends GenericFilterBean {
|
|||
HttpSession session = request.getSession();
|
||||
|
||||
// skip everything that's not an authorize URL
|
||||
if (!request.getServletPath().startsWith("/authorize")) {
|
||||
if (!requestMatcher.matches(request)) {
|
||||
chain.doFilter(req, res);
|
||||
return;
|
||||
}
|
||||
|
@ -245,4 +249,18 @@ public class AuthorizationRequestFilter extends GenericFilterBean {
|
|||
return requestMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the requestMatcher
|
||||
*/
|
||||
public RequestMatcher getRequestMatcher() {
|
||||
return requestMatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param requestMatcher the requestMatcher to set
|
||||
*/
|
||||
public void setRequestMatcher(RequestMatcher requestMatcher) {
|
||||
this.requestMatcher = requestMatcher;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,11 +17,13 @@
|
|||
|
||||
package org.mitre.openid.connect.filter;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.security.web.util.UrlUtils;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
@ -32,42 +34,26 @@ import com.google.common.collect.ImmutableSet;
|
|||
*
|
||||
*/
|
||||
public class MultiUrlRequestMatcher implements RequestMatcher {
|
||||
private final Set<String> filterProcessesUrls;
|
||||
private final Set<RequestMatcher> matchers;
|
||||
|
||||
public MultiUrlRequestMatcher(Set<String> filterProcessesUrls) {
|
||||
this.matchers = new HashSet<>(filterProcessesUrls.size());
|
||||
for (String filterProcessesUrl : filterProcessesUrls) {
|
||||
Assert.hasLength(filterProcessesUrl, "filterProcessesUrl must be specified");
|
||||
Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl), filterProcessesUrl + " isn't a valid redirect URL");
|
||||
Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl), filterProcessesUrl + " isn't a valid URL");
|
||||
matchers.add(new AntPathRequestMatcher(filterProcessesUrl));
|
||||
}
|
||||
this.filterProcessesUrls = ImmutableSet.copyOf(filterProcessesUrls);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
String uri = request.getRequestURI();
|
||||
int pathParamIndex = uri.indexOf(';');
|
||||
|
||||
if (pathParamIndex > 0) {
|
||||
// strip everything after the first semi-colon
|
||||
uri = uri.substring(0, pathParamIndex);
|
||||
}
|
||||
|
||||
if ("".equals(request.getContextPath())) {
|
||||
// if any one of the URLs match, return true
|
||||
for (String filterProcessesUrl : filterProcessesUrls) {
|
||||
if (uri.endsWith(filterProcessesUrl)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String filterProcessesUrl : filterProcessesUrls) {
|
||||
if (uri.endsWith(request.getContextPath() + filterProcessesUrl)) {
|
||||
for (RequestMatcher matcher : matchers) {
|
||||
if (matcher.matches(request)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.mitre.openid.connect.request;
|
|||
|
||||
|
||||
import static org.mitre.openid.connect.request.ConnectRequestParameters.*;
|
||||
import static org.mitre.openid.connect.request.ConnectRequestParameters.CLAIMS;
|
||||
import static org.mitre.openid.connect.request.ConnectRequestParameters.CLIENT_ID;
|
||||
import static org.mitre.openid.connect.request.ConnectRequestParameters.DISPLAY;
|
||||
import static org.mitre.openid.connect.request.ConnectRequestParameters.LOGIN_HINT;
|
||||
|
@ -30,6 +31,7 @@ import static org.mitre.openid.connect.request.ConnectRequestParameters.RESPONSE
|
|||
import static org.mitre.openid.connect.request.ConnectRequestParameters.SCOPE;
|
||||
import static org.mitre.openid.connect.request.ConnectRequestParameters.STATE;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.text.ParseException;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
@ -39,6 +41,7 @@ import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService;
|
|||
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
|
||||
import org.mitre.jwt.signer.service.impl.ClientKeyCacheService;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.oauth2.model.PKCEAlgorithm;
|
||||
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
||||
import org.mitre.oauth2.service.SystemScopeService;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -88,9 +91,8 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
|
|||
|
||||
/**
|
||||
* Constructor with arguments
|
||||
*
|
||||
*
|
||||
* @param clientDetailsService
|
||||
* @param nonceService
|
||||
*/
|
||||
@Autowired
|
||||
public ConnectOAuth2RequestFactory(ClientDetailsEntityService clientDetailsService) {
|
||||
|
@ -137,7 +139,17 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
|
|||
request.getExtensions().put(AUD, inputParams.get(AUD));
|
||||
}
|
||||
|
||||
if (inputParams.containsKey(CODE_CHALLENGE)) {
|
||||
request.getExtensions().put(CODE_CHALLENGE, inputParams.get(CODE_CHALLENGE));
|
||||
if (inputParams.containsKey(CODE_CHALLENGE_METHOD)) {
|
||||
request.getExtensions().put(CODE_CHALLENGE_METHOD, inputParams.get(CODE_CHALLENGE_METHOD));
|
||||
} else {
|
||||
// if the client doesn't specify a code challenge transformation method, it's "plain"
|
||||
request.getExtensions().put(CODE_CHALLENGE_METHOD, PKCEAlgorithm.plain.getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (inputParams.containsKey(REQUEST)) {
|
||||
request.getExtensions().put(REQUEST, inputParams.get(REQUEST));
|
||||
processRequestObject(inputParams.get(REQUEST), request);
|
||||
|
@ -164,9 +176,10 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param inputParams
|
||||
* @return
|
||||
*/
|
||||
*
|
||||
* @param jwtString
|
||||
* @param request
|
||||
*/
|
||||
private void processRequestObject(String jwtString, AuthorizationRequest request) {
|
||||
|
||||
// parse the request object
|
||||
|
@ -267,7 +280,7 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
|
|||
JWTClaimsSet claims = jwt.getJWTClaimsSet();
|
||||
|
||||
Set<String> responseTypes = OAuth2Utils.parseParameterList(claims.getStringClaim(RESPONSE_TYPE));
|
||||
if (responseTypes != null && !responseTypes.isEmpty()) {
|
||||
if (!responseTypes.isEmpty()) {
|
||||
if (!responseTypes.equals(request.getResponseTypes())) {
|
||||
logger.info("Mismatch between request object and regular parameter for response_type, using request object");
|
||||
}
|
||||
|
@ -315,7 +328,7 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
|
|||
}
|
||||
|
||||
Set<String> scope = OAuth2Utils.parseParameterList(claims.getStringClaim(SCOPE));
|
||||
if (scope != null && !scope.isEmpty()) {
|
||||
if (!scope.isEmpty()) {
|
||||
if (!scope.equals(request.getScope())) {
|
||||
logger.info("Mismatch between request object and regular parameter for scope, using request object");
|
||||
}
|
||||
|
@ -324,7 +337,8 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
|
|||
|
||||
JsonObject claimRequest = parseClaimRequest(claims.getStringClaim(CLAIMS));
|
||||
if (claimRequest != null) {
|
||||
if (!claimRequest.equals(parseClaimRequest(request.getExtensions().get(CLAIMS).toString()))) {
|
||||
Serializable claimExtension = request.getExtensions().get(CLAIMS);
|
||||
if (claimExtension == null || !claimRequest.equals(parseClaimRequest(claimExtension.toString()))) {
|
||||
logger.info("Mismatch between request object and regular parameter for claims, using request object");
|
||||
}
|
||||
// we save the string because the object might not be a Java Serializable, and we can parse it easily enough anyway
|
||||
|
|
|
@ -46,5 +46,10 @@ public interface ConnectRequestParameters {
|
|||
|
||||
// audience
|
||||
public String AUD = "aud";
|
||||
|
||||
|
||||
// PKCE
|
||||
public String CODE_CHALLENGE = "code_challenge";
|
||||
public String CODE_CHALLENGE_METHOD = "code_challenge_method";
|
||||
public String CODE_VERIFIER = "code_verifier";
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
*******************************************************************************/
|
||||
package org.mitre.openid.connect.service.impl;
|
||||
|
||||
import static org.mitre.openid.connect.request.ConnectRequestParameters.MAX_AGE;
|
||||
import static org.mitre.openid.connect.request.ConnectRequestParameters.NONCE;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -58,7 +61,6 @@ import com.nimbusds.jwt.JWT;
|
|||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.PlainJWT;
|
||||
import com.nimbusds.jwt.SignedJWT;
|
||||
|
||||
/**
|
||||
* Default implementation of service to create specialty OpenID Connect tokens.
|
||||
*
|
||||
|
@ -105,7 +107,7 @@ public class DefaultOIDCTokenService implements OIDCTokenService {
|
|||
JWTClaimsSet.Builder idClaims = new JWTClaimsSet.Builder();
|
||||
|
||||
// if the auth time claim was explicitly requested OR if the client always wants the auth time, put it in
|
||||
if (request.getExtensions().containsKey("max_age")
|
||||
if (request.getExtensions().containsKey(MAX_AGE)
|
||||
|| (request.getExtensions().containsKey("idtoken")) // TODO: parse the ID Token claims (#473) -- for now assume it could be in there
|
||||
|| (client.getRequireAuthTime() != null && client.getRequireAuthTime())) {
|
||||
|
||||
|
@ -134,7 +136,7 @@ public class DefaultOIDCTokenService implements OIDCTokenService {
|
|||
idClaims.audience(Lists.newArrayList(client.getClientId()));
|
||||
idClaims.jwtID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it
|
||||
|
||||
String nonce = (String)request.getExtensions().get("nonce");
|
||||
String nonce = (String)request.getExtensions().get(NONCE);
|
||||
if (!Strings.isNullOrEmpty(nonce)) {
|
||||
idClaims.claim("nonce", nonce);
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ import com.nimbusds.jose.EncryptionMethod;
|
|||
import com.nimbusds.jose.JWEAlgorithm;
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jwt.JWT;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -106,6 +107,17 @@ public abstract class AbstractClientEntityView extends AbstractView {
|
|||
}
|
||||
}
|
||||
})
|
||||
.registerTypeAdapter(JWT.class, new JsonSerializer<JWT>() {
|
||||
@Override
|
||||
public JsonElement serialize(JWT src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
if (src != null) {
|
||||
return new JsonPrimitive(src.serialize());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
.serializeNulls()
|
||||
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
|
||||
.create();
|
||||
|
|
|
@ -82,6 +82,7 @@ public class JsonEntityView extends AbstractView {
|
|||
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {
|
||||
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
|
||||
|
||||
HttpStatus code = (HttpStatus) model.get(HttpCodeView.CODE);
|
||||
|
|
|
@ -17,13 +17,21 @@
|
|||
package org.mitre.openid.connect.web;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.sql.SQLIntegrityConstraintViolationException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import org.eclipse.persistence.exceptions.DatabaseException;
|
||||
import org.mitre.jwt.assertion.AssertionValidator;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity.AppType;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity.SubjectType;
|
||||
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
||||
import org.mitre.oauth2.web.AuthenticationUtilities;
|
||||
import org.mitre.openid.connect.exception.ValidationException;
|
||||
import org.mitre.openid.connect.model.CachedImage;
|
||||
import org.mitre.openid.connect.service.ClientLogoLoadingService;
|
||||
import org.mitre.openid.connect.view.ClientEntityViewForAdmins;
|
||||
|
@ -34,12 +42,14 @@ import org.mitre.openid.connect.view.JsonErrorView;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.common.util.OAuth2Utils;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
@ -49,6 +59,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
|||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
|
@ -57,12 +68,57 @@ import com.google.gson.JsonElement;
|
|||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.nimbusds.jose.Algorithm;
|
||||
import com.nimbusds.jose.EncryptionMethod;
|
||||
import com.nimbusds.jose.JWEAlgorithm;
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jwt.JWT;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.JWTParser;
|
||||
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.APPLICATION_TYPE;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.CLAIMS_REDIRECT_URIS;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID_ISSUED_AT;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_NAME;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET_EXPIRES_AT;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_URI;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.CONTACTS;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_ACR_VALUES;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_MAX_AGE;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.GRANT_TYPES;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ALG;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ENC;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_SIGNED_RESPONSE_ALG;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.INITIATE_LOGIN_URI;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.JWKS;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.JWKS_URI;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.LOGO_URI;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.POLICY_URI;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.POST_LOGOUT_REDIRECT_URIS;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.REDIRECT_URIS;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.REGISTRATION_ACCESS_TOKEN;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.REGISTRATION_CLIENT_URI;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.REQUEST_OBJECT_SIGNING_ALG;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.REQUEST_URIS;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.REQUIRE_AUTH_TIME;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.RESPONSE_TYPES;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.SCOPE;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.SECTOR_IDENTIFIER_URI;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.SOFTWARE_STATEMENT;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.SUBJECT_TYPE;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_METHOD;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_SIGNING_ALG;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.TOS_URI;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ALG;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ENC;
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_SIGNED_RESPONSE_ALG;
|
||||
|
||||
/**
|
||||
* @author Michael Jett <mjett@mitre.org>
|
||||
|
@ -80,6 +136,10 @@ public class ClientAPI {
|
|||
|
||||
@Autowired
|
||||
private ClientLogoLoadingService clientLogoLoadingService;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("clientAssertionValidator")
|
||||
private AssertionValidator assertionValidator;
|
||||
|
||||
private JsonParser parser = new JsonParser();
|
||||
|
||||
|
@ -129,6 +189,20 @@ public class ClientAPI {
|
|||
}
|
||||
}
|
||||
})
|
||||
.registerTypeAdapter(JWT.class, new JsonDeserializer<JWT>() {
|
||||
@Override
|
||||
public JWT deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
if (json.isJsonPrimitive()) {
|
||||
try {
|
||||
return JWTParser.parse(json.getAsString());
|
||||
} catch (ParseException e) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
})
|
||||
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
|
||||
.create();
|
||||
|
||||
|
@ -172,8 +246,8 @@ public class ClientAPI {
|
|||
try {
|
||||
json = parser.parse(jsonString).getAsJsonObject();
|
||||
client = gson.fromJson(json, ClientDetailsEntity.class);
|
||||
}
|
||||
catch (JsonSyntaxException e) {
|
||||
client = validateSoftwareStatement(client);
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.error("apiAddClient failed due to JsonSyntaxException", e);
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
|
||||
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Could not save new client. The server encountered a JSON syntax exception. Contact a system administrator for assistance.");
|
||||
|
@ -183,6 +257,11 @@ public class ClientAPI {
|
|||
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
|
||||
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Could not save new client. The server encountered an IllegalStateException. Refresh and try again - if the problem persists, contact a system administrator for assistance.");
|
||||
return JsonErrorView.VIEWNAME;
|
||||
} catch (ValidationException e) {
|
||||
logger.error("apiUpdateClient failed due to ValidationException", e);
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
|
||||
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Could not update client. The server encountered a ValidationException.");
|
||||
return JsonErrorView.VIEWNAME;
|
||||
}
|
||||
|
||||
// if they leave the client identifier empty, force it to be generated
|
||||
|
@ -244,6 +323,18 @@ public class ClientAPI {
|
|||
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
|
||||
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Unable to save client: " + e.getMessage());
|
||||
return JsonErrorView.VIEWNAME;
|
||||
} catch (PersistenceException e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof DatabaseException) {
|
||||
Throwable databaseExceptionCause = cause.getCause();
|
||||
if(databaseExceptionCause instanceof SQLIntegrityConstraintViolationException) {
|
||||
logger.error("apiAddClient failed; duplicate client id entry found: {}", client.getClientId());
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.CONFLICT);
|
||||
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Unable to save client. Duplicate client id entry found: " + client.getClientId());
|
||||
return JsonErrorView.VIEWNAME;
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,8 +357,8 @@ public class ClientAPI {
|
|||
// parse the client passed in (from JSON) and fetch the old client from the store
|
||||
json = parser.parse(jsonString).getAsJsonObject();
|
||||
client = gson.fromJson(json, ClientDetailsEntity.class);
|
||||
}
|
||||
catch (JsonSyntaxException e) {
|
||||
client = validateSoftwareStatement(client);
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.error("apiUpdateClient failed due to JsonSyntaxException", e);
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
|
||||
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Could not update client. The server encountered a JSON syntax exception. Contact a system administrator for assistance.");
|
||||
|
@ -277,6 +368,11 @@ public class ClientAPI {
|
|||
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
|
||||
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Could not update client. The server encountered an IllegalStateException. Refresh and try again - if the problem persists, contact a system administrator for assistance.");
|
||||
return JsonErrorView.VIEWNAME;
|
||||
} catch (ValidationException e) {
|
||||
logger.error("apiUpdateClient failed due to ValidationException", e);
|
||||
m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
|
||||
m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Could not update client. The server encountered a ValidationException.");
|
||||
return JsonErrorView.VIEWNAME;
|
||||
}
|
||||
|
||||
ClientDetailsEntity oldClient = clientService.getClientById(id);
|
||||
|
@ -400,14 +496,14 @@ public class ClientAPI {
|
|||
return ClientEntityViewForUsers.VIEWNAME;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the logo image for a client
|
||||
* @param id
|
||||
*/
|
||||
@RequestMapping(value = "/{id}/logo", method=RequestMethod.GET, produces = { MediaType.IMAGE_GIF_VALUE, MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE })
|
||||
public ResponseEntity<byte[]> getClientLogo(@PathVariable("id") Long id, Model model) {
|
||||
|
||||
|
||||
ClientDetailsEntity client = clientService.getClientById(id);
|
||||
|
||||
if (client == null) {
|
||||
|
@ -417,13 +513,151 @@ public class ClientAPI {
|
|||
} else {
|
||||
// get the image from cache
|
||||
CachedImage image = clientLogoLoadingService.getLogo(client);
|
||||
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.parseMediaType(image.getContentType()));
|
||||
headers.setContentLength(image.getLength());
|
||||
|
||||
|
||||
return new ResponseEntity<>(image.getData(), headers, HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
|
||||
private ClientDetailsEntity validateSoftwareStatement(ClientDetailsEntity newClient) throws ValidationException {
|
||||
if (newClient.getSoftwareStatement() != null) {
|
||||
if (assertionValidator.isValid(newClient.getSoftwareStatement())) {
|
||||
// we have a software statement and its envelope passed all the checks from our validator
|
||||
|
||||
// swap out all of the client's fields for the associated parts of the software statement
|
||||
try {
|
||||
JWTClaimsSet claimSet = newClient.getSoftwareStatement().getJWTClaimsSet();
|
||||
for (String claim : claimSet.getClaims().keySet()) {
|
||||
switch (claim) {
|
||||
case SOFTWARE_STATEMENT:
|
||||
throw new ValidationException("invalid_client_metadata", "Software statement can't include another software statement", HttpStatus.BAD_REQUEST);
|
||||
case CLAIMS_REDIRECT_URIS:
|
||||
newClient.setClaimsRedirectUris(Sets.newHashSet(claimSet.getStringListClaim(claim)));
|
||||
break;
|
||||
case CLIENT_SECRET_EXPIRES_AT:
|
||||
throw new ValidationException("invalid_client_metadata", "Software statement can't include a client secret expiration time", HttpStatus.BAD_REQUEST);
|
||||
case CLIENT_ID_ISSUED_AT:
|
||||
throw new ValidationException("invalid_client_metadata", "Software statement can't include a client ID issuance time", HttpStatus.BAD_REQUEST);
|
||||
case REGISTRATION_CLIENT_URI:
|
||||
throw new ValidationException("invalid_client_metadata", "Software statement can't include a client configuration endpoint", HttpStatus.BAD_REQUEST);
|
||||
case REGISTRATION_ACCESS_TOKEN:
|
||||
throw new ValidationException("invalid_client_metadata", "Software statement can't include a client registration access token", HttpStatus.BAD_REQUEST);
|
||||
case REQUEST_URIS:
|
||||
newClient.setRequestUris(Sets.newHashSet(claimSet.getStringListClaim(claim)));
|
||||
break;
|
||||
case POST_LOGOUT_REDIRECT_URIS:
|
||||
newClient.setPostLogoutRedirectUris(Sets.newHashSet(claimSet.getStringListClaim(claim)));
|
||||
break;
|
||||
case INITIATE_LOGIN_URI:
|
||||
newClient.setInitiateLoginUri(claimSet.getStringClaim(claim));
|
||||
break;
|
||||
case DEFAULT_ACR_VALUES:
|
||||
newClient.setDefaultACRvalues(Sets.newHashSet(claimSet.getStringListClaim(claim)));
|
||||
break;
|
||||
case REQUIRE_AUTH_TIME:
|
||||
newClient.setRequireAuthTime(claimSet.getBooleanClaim(claim));
|
||||
break;
|
||||
case DEFAULT_MAX_AGE:
|
||||
newClient.setDefaultMaxAge(claimSet.getIntegerClaim(claim));
|
||||
break;
|
||||
case TOKEN_ENDPOINT_AUTH_SIGNING_ALG:
|
||||
newClient.setTokenEndpointAuthSigningAlg(JWSAlgorithm.parse(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case ID_TOKEN_ENCRYPTED_RESPONSE_ENC:
|
||||
newClient.setIdTokenEncryptedResponseEnc(EncryptionMethod.parse(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case ID_TOKEN_ENCRYPTED_RESPONSE_ALG:
|
||||
newClient.setIdTokenEncryptedResponseAlg(JWEAlgorithm.parse(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case ID_TOKEN_SIGNED_RESPONSE_ALG:
|
||||
newClient.setIdTokenSignedResponseAlg(JWSAlgorithm.parse(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case USERINFO_ENCRYPTED_RESPONSE_ENC:
|
||||
newClient.setUserInfoEncryptedResponseEnc(EncryptionMethod.parse(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case USERINFO_ENCRYPTED_RESPONSE_ALG:
|
||||
newClient.setUserInfoEncryptedResponseAlg(JWEAlgorithm.parse(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case USERINFO_SIGNED_RESPONSE_ALG:
|
||||
newClient.setUserInfoSignedResponseAlg(JWSAlgorithm.parse(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case REQUEST_OBJECT_SIGNING_ALG:
|
||||
newClient.setRequestObjectSigningAlg(JWSAlgorithm.parse(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case SUBJECT_TYPE:
|
||||
newClient.setSubjectType(SubjectType.getByValue(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case SECTOR_IDENTIFIER_URI:
|
||||
newClient.setSectorIdentifierUri(claimSet.getStringClaim(claim));
|
||||
break;
|
||||
case APPLICATION_TYPE:
|
||||
newClient.setApplicationType(AppType.getByValue(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case JWKS_URI:
|
||||
newClient.setJwksUri(claimSet.getStringClaim(claim));
|
||||
break;
|
||||
case JWKS:
|
||||
newClient.setJwks(JWKSet.parse(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case POLICY_URI:
|
||||
newClient.setPolicyUri(claimSet.getStringClaim(claim));
|
||||
break;
|
||||
case RESPONSE_TYPES:
|
||||
newClient.setResponseTypes(Sets.newHashSet(claimSet.getStringListClaim(claim)));
|
||||
break;
|
||||
case GRANT_TYPES:
|
||||
newClient.setGrantTypes(Sets.newHashSet(claimSet.getStringListClaim(claim)));
|
||||
break;
|
||||
case SCOPE:
|
||||
newClient.setScope(OAuth2Utils.parseParameterList(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case TOKEN_ENDPOINT_AUTH_METHOD:
|
||||
newClient.setTokenEndpointAuthMethod(AuthMethod.getByValue(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case TOS_URI:
|
||||
newClient.setTosUri(claimSet.getStringClaim(claim));
|
||||
break;
|
||||
case CONTACTS:
|
||||
newClient.setContacts(Sets.newHashSet(claimSet.getStringListClaim(claim)));
|
||||
break;
|
||||
case LOGO_URI:
|
||||
newClient.setLogoUri(claimSet.getStringClaim(claim));
|
||||
break;
|
||||
case CLIENT_URI:
|
||||
newClient.setClientUri(claimSet.getStringClaim(claim));
|
||||
break;
|
||||
case CLIENT_NAME:
|
||||
newClient.setClientName(claimSet.getStringClaim(claim));
|
||||
break;
|
||||
case REDIRECT_URIS:
|
||||
newClient.setRedirectUris(Sets.newHashSet(claimSet.getStringListClaim(claim)));
|
||||
break;
|
||||
case CLIENT_SECRET:
|
||||
throw new ValidationException("invalid_client_metadata", "Software statement can't contain client secret", HttpStatus.BAD_REQUEST);
|
||||
case CLIENT_ID:
|
||||
throw new ValidationException("invalid_client_metadata", "Software statement can't contain client ID", HttpStatus.BAD_REQUEST);
|
||||
|
||||
default:
|
||||
logger.warn("Software statement contained unknown field: " + claim + " with value " + claimSet.getClaim(claim));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return newClient;
|
||||
} catch (ParseException e) {
|
||||
throw new ValidationException("invalid_client_metadata", "Software statement claims didn't parse", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
} else {
|
||||
throw new ValidationException("invalid_client_metadata", "Software statement rejected by validator", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
} else {
|
||||
// nothing to see here, carry on
|
||||
return newClient;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
*******************************************************************************/
|
||||
package org.mitre.openid.connect.web;
|
||||
|
||||
import static org.mitre.oauth2.model.RegisteredClientFields.*;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
|
@ -23,9 +25,12 @@ import java.util.HashSet;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.mitre.jwt.assertion.AssertionValidator;
|
||||
import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity.AppType;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
|
||||
import org.mitre.oauth2.model.ClientDetailsEntity.SubjectType;
|
||||
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
|
||||
import org.mitre.oauth2.model.RegisteredClient;
|
||||
import org.mitre.oauth2.model.SystemScope;
|
||||
|
@ -43,9 +48,11 @@ import org.mitre.openid.connect.view.JsonErrorView;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.oauth2.common.util.OAuth2Utils;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
@ -60,6 +67,11 @@ import com.google.common.base.Strings;
|
|||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.nimbusds.jose.EncryptionMethod;
|
||||
import com.nimbusds.jose.JWEAlgorithm;
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
|
||||
@Controller
|
||||
@RequestMapping(value = DynamicClientRegistrationEndpoint.URL)
|
||||
|
@ -88,6 +100,10 @@ public class DynamicClientRegistrationEndpoint {
|
|||
@Autowired
|
||||
private OIDCTokenService connectTokenService;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("clientAssertionValidator")
|
||||
private AssertionValidator assertionValidator;
|
||||
|
||||
/**
|
||||
* Logger for this class
|
||||
*/
|
||||
|
@ -127,6 +143,7 @@ public class DynamicClientRegistrationEndpoint {
|
|||
|
||||
// do validation on the fields
|
||||
try {
|
||||
newClient = validateSoftwareStatement(newClient); // need to handle the software statement first because it might override requested values
|
||||
newClient = validateScopes(newClient);
|
||||
newClient = validateResponseTypes(newClient);
|
||||
newClient = validateGrantTypes(newClient);
|
||||
|
@ -234,7 +251,7 @@ public class DynamicClientRegistrationEndpoint {
|
|||
if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) {
|
||||
|
||||
try {
|
||||
OAuth2AccessTokenEntity token = fetchValidRegistrationToken(auth, client);
|
||||
OAuth2AccessTokenEntity token = rotateRegistrationTokenIfNecessary(auth, client);
|
||||
RegisteredClient registered = new RegisteredClient(client, token.getValue(), config.getIssuer() + "register/" + UriUtils.encodePathSegment(client.getClientId(), "UTF-8"));
|
||||
|
||||
// send it all out to the view
|
||||
|
@ -304,6 +321,7 @@ public class DynamicClientRegistrationEndpoint {
|
|||
|
||||
// do validation on the fields
|
||||
try {
|
||||
newClient = validateSoftwareStatement(newClient); // need to handle the software statement first because it might override requested values
|
||||
newClient = validateScopes(newClient);
|
||||
newClient = validateResponseTypes(newClient);
|
||||
newClient = validateGrantTypes(newClient);
|
||||
|
@ -321,7 +339,7 @@ public class DynamicClientRegistrationEndpoint {
|
|||
// save the client
|
||||
ClientDetailsEntity savedClient = clientService.updateClient(oldClient, newClient);
|
||||
|
||||
OAuth2AccessTokenEntity token = fetchValidRegistrationToken(auth, savedClient);
|
||||
OAuth2AccessTokenEntity token = rotateRegistrationTokenIfNecessary(auth, savedClient);
|
||||
|
||||
RegisteredClient registered = new RegisteredClient(savedClient, token.getValue(), config.getIssuer() + "register/" + UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8"));
|
||||
|
||||
|
@ -553,7 +571,155 @@ public class DynamicClientRegistrationEndpoint {
|
|||
return newClient;
|
||||
}
|
||||
|
||||
private OAuth2AccessTokenEntity fetchValidRegistrationToken(OAuth2Authentication auth, ClientDetailsEntity client) {
|
||||
|
||||
/**
|
||||
* @param newClient
|
||||
* @return
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private ClientDetailsEntity validateSoftwareStatement(ClientDetailsEntity newClient) throws ValidationException {
|
||||
if (newClient.getSoftwareStatement() != null) {
|
||||
if (assertionValidator.isValid(newClient.getSoftwareStatement())) {
|
||||
// we have a software statement and its envelope passed all the checks from our validator
|
||||
|
||||
// swap out all of the client's fields for the associated parts of the software statement
|
||||
try {
|
||||
JWTClaimsSet claimSet = newClient.getSoftwareStatement().getJWTClaimsSet();
|
||||
for (String claim : claimSet.getClaims().keySet()) {
|
||||
switch (claim) {
|
||||
case SOFTWARE_STATEMENT:
|
||||
throw new ValidationException("invalid_client_metadata", "Software statement can't include another software statement", HttpStatus.BAD_REQUEST);
|
||||
case CLAIMS_REDIRECT_URIS:
|
||||
newClient.setClaimsRedirectUris(Sets.newHashSet(claimSet.getStringListClaim(claim)));
|
||||
break;
|
||||
case CLIENT_SECRET_EXPIRES_AT:
|
||||
throw new ValidationException("invalid_client_metadata", "Software statement can't include a client secret expiration time", HttpStatus.BAD_REQUEST);
|
||||
case CLIENT_ID_ISSUED_AT:
|
||||
throw new ValidationException("invalid_client_metadata", "Software statement can't include a client ID issuance time", HttpStatus.BAD_REQUEST);
|
||||
case REGISTRATION_CLIENT_URI:
|
||||
throw new ValidationException("invalid_client_metadata", "Software statement can't include a client configuration endpoint", HttpStatus.BAD_REQUEST);
|
||||
case REGISTRATION_ACCESS_TOKEN:
|
||||
throw new ValidationException("invalid_client_metadata", "Software statement can't include a client registration access token", HttpStatus.BAD_REQUEST);
|
||||
case REQUEST_URIS:
|
||||
newClient.setRequestUris(Sets.newHashSet(claimSet.getStringListClaim(claim)));
|
||||
break;
|
||||
case POST_LOGOUT_REDIRECT_URIS:
|
||||
newClient.setPostLogoutRedirectUris(Sets.newHashSet(claimSet.getStringListClaim(claim)));
|
||||
break;
|
||||
case INITIATE_LOGIN_URI:
|
||||
newClient.setInitiateLoginUri(claimSet.getStringClaim(claim));
|
||||
break;
|
||||
case DEFAULT_ACR_VALUES:
|
||||
newClient.setDefaultACRvalues(Sets.newHashSet(claimSet.getStringListClaim(claim)));
|
||||
break;
|
||||
case REQUIRE_AUTH_TIME:
|
||||
newClient.setRequireAuthTime(claimSet.getBooleanClaim(claim));
|
||||
break;
|
||||
case DEFAULT_MAX_AGE:
|
||||
newClient.setDefaultMaxAge(claimSet.getIntegerClaim(claim));
|
||||
break;
|
||||
case TOKEN_ENDPOINT_AUTH_SIGNING_ALG:
|
||||
newClient.setTokenEndpointAuthSigningAlg(JWSAlgorithm.parse(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case ID_TOKEN_ENCRYPTED_RESPONSE_ENC:
|
||||
newClient.setIdTokenEncryptedResponseEnc(EncryptionMethod.parse(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case ID_TOKEN_ENCRYPTED_RESPONSE_ALG:
|
||||
newClient.setIdTokenEncryptedResponseAlg(JWEAlgorithm.parse(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case ID_TOKEN_SIGNED_RESPONSE_ALG:
|
||||
newClient.setIdTokenSignedResponseAlg(JWSAlgorithm.parse(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case USERINFO_ENCRYPTED_RESPONSE_ENC:
|
||||
newClient.setUserInfoEncryptedResponseEnc(EncryptionMethod.parse(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case USERINFO_ENCRYPTED_RESPONSE_ALG:
|
||||
newClient.setUserInfoEncryptedResponseAlg(JWEAlgorithm.parse(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case USERINFO_SIGNED_RESPONSE_ALG:
|
||||
newClient.setUserInfoSignedResponseAlg(JWSAlgorithm.parse(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case REQUEST_OBJECT_SIGNING_ALG:
|
||||
newClient.setRequestObjectSigningAlg(JWSAlgorithm.parse(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case SUBJECT_TYPE:
|
||||
newClient.setSubjectType(SubjectType.getByValue(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case SECTOR_IDENTIFIER_URI:
|
||||
newClient.setSectorIdentifierUri(claimSet.getStringClaim(claim));
|
||||
break;
|
||||
case APPLICATION_TYPE:
|
||||
newClient.setApplicationType(AppType.getByValue(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case JWKS_URI:
|
||||
newClient.setJwksUri(claimSet.getStringClaim(claim));
|
||||
break;
|
||||
case JWKS:
|
||||
newClient.setJwks(JWKSet.parse(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case POLICY_URI:
|
||||
newClient.setPolicyUri(claimSet.getStringClaim(claim));
|
||||
break;
|
||||
case RESPONSE_TYPES:
|
||||
newClient.setResponseTypes(Sets.newHashSet(claimSet.getStringListClaim(claim)));
|
||||
break;
|
||||
case GRANT_TYPES:
|
||||
newClient.setGrantTypes(Sets.newHashSet(claimSet.getStringListClaim(claim)));
|
||||
break;
|
||||
case SCOPE:
|
||||
newClient.setScope(OAuth2Utils.parseParameterList(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case TOKEN_ENDPOINT_AUTH_METHOD:
|
||||
newClient.setTokenEndpointAuthMethod(AuthMethod.getByValue(claimSet.getStringClaim(claim)));
|
||||
break;
|
||||
case TOS_URI:
|
||||
newClient.setTosUri(claimSet.getStringClaim(claim));
|
||||
break;
|
||||
case CONTACTS:
|
||||
newClient.setContacts(Sets.newHashSet(claimSet.getStringListClaim(claim)));
|
||||
break;
|
||||
case LOGO_URI:
|
||||
newClient.setLogoUri(claimSet.getStringClaim(claim));
|
||||
break;
|
||||
case CLIENT_URI:
|
||||
newClient.setClientUri(claimSet.getStringClaim(claim));
|
||||
break;
|
||||
case CLIENT_NAME:
|
||||
newClient.setClientName(claimSet.getStringClaim(claim));
|
||||
break;
|
||||
case REDIRECT_URIS:
|
||||
newClient.setRedirectUris(Sets.newHashSet(claimSet.getStringListClaim(claim)));
|
||||
break;
|
||||
case CLIENT_SECRET:
|
||||
throw new ValidationException("invalid_client_metadata", "Software statement can't contain client secret", HttpStatus.BAD_REQUEST);
|
||||
case CLIENT_ID:
|
||||
throw new ValidationException("invalid_client_metadata", "Software statement can't contain client ID", HttpStatus.BAD_REQUEST);
|
||||
|
||||
default:
|
||||
logger.warn("Software statement contained unknown field: " + claim + " with value " + claimSet.getClaim(claim));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return newClient;
|
||||
} catch (ParseException e) {
|
||||
throw new ValidationException("invalid_client_metadata", "Software statement claims didn't parse", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
} else {
|
||||
throw new ValidationException("invalid_client_metadata", "Software statement rejected by validator", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
} else {
|
||||
// nothing to see here, carry on
|
||||
return newClient;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Rotates the registration token if it's expired, otherwise returns it
|
||||
*/
|
||||
private OAuth2AccessTokenEntity rotateRegistrationTokenIfNecessary(OAuth2Authentication auth, ClientDetailsEntity client) {
|
||||
|
||||
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails();
|
||||
OAuth2AccessTokenEntity token = tokenService.readAccessToken(details.getTokenValue());
|
||||
|
|
37
pom.xml
37
pom.xml
|
@ -19,7 +19,7 @@
|
|||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.mitre</groupId>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<version>1.2.7.cnaf-SNAPSHOT</version>
|
||||
<version>1.3.0.cnaf-SNAPSHOT</version>
|
||||
<name>MITREid Connect</name>
|
||||
<packaging>pom</packaging>
|
||||
<parent>
|
||||
|
@ -69,7 +69,9 @@
|
|||
</mailingLists>
|
||||
|
||||
<properties>
|
||||
<java-version>1.7</java-version>
|
||||
<java-version>1.8</java-version>
|
||||
<org.springframework-version>4.1.1.RELEASE</org.springframework-version>
|
||||
<spring.security.version>3.2.5.RELEASE</spring.security.version>
|
||||
<org.slf4j-version>1.7.12</org.slf4j-version>
|
||||
</properties>
|
||||
<description>A reference implementation of OpenID Connect (http://openid.net/connect/), OAuth 2.0, and UMA built on top of Java, Spring, and Spring Security. The project contains a fully functioning server, client, and utility library.</description>
|
||||
|
@ -142,6 +144,7 @@
|
|||
<windowtitle>MITREid Connect v. ${project.version}</windowtitle>
|
||||
<doctitle>MITREid Connect v. ${project.version}</doctitle>
|
||||
<overview>${basedir}/src/main/javadoc/overview.html</overview>
|
||||
<additionalparam>-Xdoclint:none</additionalparam>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
|
@ -494,7 +497,12 @@
|
|||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>[1.52,]</version>
|
||||
<version>[1.52,)</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.persistence</groupId>
|
||||
<artifactId>org.eclipse.persistence.core</artifactId>
|
||||
<version>2.5.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
@ -541,27 +549,4 @@
|
|||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<!-- Turn off strict javadoc when building on 1.8 -->
|
||||
<activation>
|
||||
<jdk>1.8</jdk>
|
||||
</activation>
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9</version>
|
||||
<configuration>
|
||||
<additionalparam>-Xdoclint:none</additionalparam>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<parent>
|
||||
<groupId>org.mitre</groupId>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<version>1.2.7.cnaf-SNAPSHOT</version>
|
||||
<version>1.3.0.cnaf-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>uma-server-webapp</artifactId>
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"admin": {
|
||||
"policies": "管理受保护资源的政策"
|
||||
},
|
||||
"policy" : {
|
||||
"resource-sets": "资源集",
|
||||
"edit-policies": "编辑政策",
|
||||
"new-policy": "新建政策",
|
||||
"edit-policy": "编辑政策",
|
||||
"loading-policies": "政策",
|
||||
"loading-policy": "政策",
|
||||
"loading-rs": "资源集",
|
||||
"rs-table": {
|
||||
"confirm": "确定要删除该资源?",
|
||||
"no-resource-sets": "尚未有已注册的资源集。您可在此授权服务器中注册一个。",
|
||||
"scopes": "范围",
|
||||
"shared-with": "共享给:",
|
||||
"shared-nobody": "不共享",
|
||||
"shared-nobody-tooltip": "此资源别人无法访问,请编辑政策使其与其他人共享。",
|
||||
"sharing": "共享政策"
|
||||
},
|
||||
"policy-table": {
|
||||
"new": "新建政策",
|
||||
"return": "返回到列表",
|
||||
"edit": "编辑政策",
|
||||
"confirm": "确定要删除该政策?",
|
||||
"delete": "删除",
|
||||
"no-policies": "此资源集尚未有政策:别人无法访问此资源集。",
|
||||
"required-claims": "必须的声明",
|
||||
"required-claims-info": "与您共享此资源的用户必须具备以下声明,才能访问该资源。",
|
||||
"remove": "移除",
|
||||
"issuers": "签发者",
|
||||
"claim": "声明项",
|
||||
"value": "值"
|
||||
},
|
||||
"policy-form": {
|
||||
"email-address": "email地址",
|
||||
"share-email": "连带email地址共享",
|
||||
"new": "新建政策",
|
||||
"edit": "编辑政策",
|
||||
"claim-name": "声明项名称",
|
||||
"friendly-claim-name": "声明的显示名",
|
||||
"claim-value": "声明的值",
|
||||
"value-type-text": "文本",
|
||||
"value-type-number": "数字",
|
||||
"clear-all": "清除全部声明",
|
||||
"clear-all-confirm": "您是否要从此政策中清除全部声明?"
|
||||
},
|
||||
"webfinger-error": "错误",
|
||||
"webfinger-error-description": "服务器无法找到<code>__email__</code>的身份提供者。",
|
||||
"advanced-error": "错误",
|
||||
"advanced-error-description": "保存高级声明时出错。您是否填写了全部必填项?"
|
||||
},
|
||||
"sidebar": {
|
||||
"personal": {
|
||||
"resource_policies": "管理受保护资源的政策"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"admin": {
|
||||
"policies": "管理受保护资源的政策"
|
||||
},
|
||||
"policy" : {
|
||||
"resource-sets": "资源集",
|
||||
"edit-policies": "编辑政策",
|
||||
"new-policy": "新建政策",
|
||||
"edit-policy": "编辑政策",
|
||||
"loading-policies": "政策",
|
||||
"loading-policy": "政策",
|
||||
"loading-rs": "资源集",
|
||||
"rs-table": {
|
||||
"confirm": "确定要删除该资源?",
|
||||
"no-resource-sets": "尚未有已注册的资源集。您可在此授权服务器中注册一个。",
|
||||
"scopes": "范围",
|
||||
"shared-with": "共享给:",
|
||||
"shared-nobody": "不共享",
|
||||
"shared-nobody-tooltip": "此资源别人无法访问,请编辑政策使其与其他人共享。",
|
||||
"sharing": "共享政策"
|
||||
},
|
||||
"policy-table": {
|
||||
"new": "新建政策",
|
||||
"return": "返回到列表",
|
||||
"edit": "编辑政策",
|
||||
"confirm": "确定要删除该政策?",
|
||||
"delete": "删除",
|
||||
"no-policies": "此资源集尚未有政策:别人无法访问此资源集。",
|
||||
"required-claims": "必须的声明",
|
||||
"required-claims-info": "与您共享此资源的用户必须具备以下声明,才能访问该资源。",
|
||||
"remove": "移除",
|
||||
"issuers": "签发者",
|
||||
"claim": "声明项",
|
||||
"value": "值"
|
||||
},
|
||||
"policy-form": {
|
||||
"email-address": "email地址",
|
||||
"share-email": "连带email地址共享",
|
||||
"new": "新建政策",
|
||||
"edit": "编辑政策",
|
||||
"claim-name": "声明项名称",
|
||||
"friendly-claim-name": "声明的显示名",
|
||||
"claim-value": "声明的值",
|
||||
"value-type-text": "文本",
|
||||
"value-type-number": "数字",
|
||||
"clear-all": "清除全部声明",
|
||||
"clear-all-confirm": "您是否要从此政策中清除全部声明?"
|
||||
},
|
||||
"webfinger-error": "错误",
|
||||
"webfinger-error-description": "服务器无法找到<code>__email__</code>的身份提供者。",
|
||||
"advanced-error": "错误",
|
||||
"advanced-error-description": "保存高级声明时出错。您是否填写了全部必填项?"
|
||||
},
|
||||
"sidebar": {
|
||||
"personal": {
|
||||
"resource_policies": "管理受保护资源的政策"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"admin": {
|
||||
"policies": "管理受保護資源的政策"
|
||||
},
|
||||
"policy" : {
|
||||
"resource-sets": "資源集",
|
||||
"edit-policies": "編輯政策",
|
||||
"new-policy": "新建政策",
|
||||
"edit-policy": "編輯政策",
|
||||
"loading-policies": "政策",
|
||||
"loading-policy": "政策",
|
||||
"loading-rs": "資源集",
|
||||
"rs-table": {
|
||||
"confirm": "確定要刪除該資源?",
|
||||
"no-resource-sets": "尚未有已注冊的資源集。您可在此授權伺服器中注冊一個。",
|
||||
"scopes": "范圍",
|
||||
"shared-with": "共享給:",
|
||||
"shared-nobody": "不共享",
|
||||
"shared-nobody-tooltip": "此資源別人無法訪問,請編輯政策使其與其他人共享。",
|
||||
"sharing": "共享政策"
|
||||
},
|
||||
"policy-table": {
|
||||
"new": "新建政策",
|
||||
"return": "返回到列表",
|
||||
"edit": "編輯政策",
|
||||
"confirm": "確定要刪除該政策?",
|
||||
"delete": "刪除",
|
||||
"no-policies": "此資源集尚未有政策:別人無法訪問此資源集。",
|
||||
"required-claims": "必須的聲明",
|
||||
"required-claims-info": "與您共享此資源的用戶必須具備以下聲明,才能訪問該資源。",
|
||||
"remove": "移除",
|
||||
"issuers": "簽發者",
|
||||
"claim": "聲明項",
|
||||
"value": "值"
|
||||
},
|
||||
"policy-form": {
|
||||
"email-address": "email地址",
|
||||
"share-email": "連帶email地址共享",
|
||||
"new": "新建政策",
|
||||
"edit": "編輯政策",
|
||||
"claim-name": "聲明項名稱",
|
||||
"friendly-claim-name": "聲明的顯示名",
|
||||
"claim-value": "聲明的值",
|
||||
"value-type-text": "文本",
|
||||
"value-type-number": "數字",
|
||||
"clear-all": "清除全部聲明",
|
||||
"clear-all-confirm": "您是否要從此政策中清除全部聲明?"
|
||||
},
|
||||
"webfinger-error": "錯誤",
|
||||
"webfinger-error-description": "伺服器無法找到<code>__email__</code>的身份提供者。",
|
||||
"advanced-error": "錯誤",
|
||||
"advanced-error-description": "保存高級聲明時出錯。您是否填寫了全部必填項?"
|
||||
},
|
||||
"sidebar": {
|
||||
"personal": {
|
||||
"resource_policies": "管理受保護資源的政策"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@
|
|||
<parent>
|
||||
<groupId>org.mitre</groupId>
|
||||
<artifactId>openid-connect-parent</artifactId>
|
||||
<version>1.2.7.cnaf-SNAPSHOT</version>
|
||||
<version>1.3.0.cnaf-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>uma-server</artifactId>
|
||||
|
|
Loading…
Reference in New Issue