Merge remote-tracking branch 'upstream/master' into devel

pull/1611/head
Marco Caberletti 2016-07-28 10:23:24 +02:00
commit 8c5f34a979
76 changed files with 3363 additions and 700 deletions

14
.editorconfig Normal file
View File

@ -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

View File

@ -1,10 +1,7 @@
language: java
jdk:
- oraclejdk8
- oraclejdk7
- openjdk7
sudo: false
after_success:
- bash <(curl -s https://codecov.io/bash)

39
README_zh_CN.md Normal file
View File

@ -0,0 +1,39 @@
# MITREid Connect
---
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.mitre/openid-connect-parent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.mitre/openid-connect-parent) [![Travis CI](https://travis-ci.org/mitreid-connect/OpenID-Connect-Java-Spring-Server.svg?branch=master)](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授权服务器。
[![OpenID认证](https://cloud.githubusercontent.com/assets/1454075/7611268/4d19de32-f97b-11e4-895b-31b2455a7ca6.png)](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/)
版权所有 &copy;2016, [ MITRE公司 ](http://www.mitre.org/)
以及 [MIT因特网信任联盟](http://www.mit-trust.org/). 采用Apache 2.0许可证, 详见 `LICENSE.txt`.

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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
*/

View File

@ -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";

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -38,7 +38,6 @@ import org.springframework.web.servlet.i18n.AbstractLocaleContextResolver;
* @author jricher
*
*/
@Component("localeResolver")
public class ConfigurationBeanLocaleResolver extends AbstractLocaleContextResolver {
@Autowired

View File

@ -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

View File

@ -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());

View File

@ -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());
}
}

View File

@ -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>

View File

@ -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)
);

View File

@ -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 -->

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -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"%>

View File

@ -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">

View File

@ -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"%>

View File

@ -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"%>

View File

@ -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"%>

View File

@ -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>

View File

@ -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"%>

View File

@ -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"%>

View File

@ -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;
}

View File

@ -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",

View File

@ -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();

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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\">&copy; 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."
}

View File

@ -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\">&copy; 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/\">了解更多信息&raquo;</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\">电子信箱 &raquo;</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": "登录失败。请重试。"
}
}

View File

@ -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\">&copy; 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/\">了解更多信息&raquo;</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\">电子信箱 &raquo;</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": "登录失败。请重试。"
}
}

View File

@ -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\">&copy; 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/\">在此瞭解更多詳情&raquo;</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\">電子信箱 &raquo;</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": "登入失敗,請重試。"
}
}

View File

@ -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;

View File

@ -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()
});
}

View File

@ -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');

View File

@ -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()
});
}

View File

@ -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>

View File

@ -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">

View File

@ -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">

View File

@ -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>

View File

@ -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;

View File

@ -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);
}

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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??
}
*/
}

View File

@ -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;
}
/**

View File

@ -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);

View File

@ -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) {

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -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";
}

View File

@ -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);
}

View File

@ -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();

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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
View File

@ -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>

View File

@ -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>

View File

@ -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": "管理受保护资源的政策"
}
}
}

View File

@ -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": "管理受保护资源的政策"
}
}
}

View File

@ -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": "管理受保護資源的政策"
}
}
}

View File

@ -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>