Updated validator structure and id token checking.
							parent
							
								
									a0df7ad04b
								
							
						
					
					
						commit
						26d5a846e0
					
				| 
						 | 
				
			
			@ -19,33 +19,25 @@ import java.io.IOException;
 | 
			
		|||
import java.io.UnsupportedEncodingException;
 | 
			
		||||
import java.math.BigInteger;
 | 
			
		||||
import java.net.URLEncoder;
 | 
			
		||||
import java.security.GeneralSecurityException;
 | 
			
		||||
import java.security.KeyPair;
 | 
			
		||||
import java.security.KeyPairGenerator;
 | 
			
		||||
import java.security.PrivateKey;
 | 
			
		||||
import java.security.PublicKey;
 | 
			
		||||
import java.security.SecureRandom;
 | 
			
		||||
import java.security.Signature;
 | 
			
		||||
import java.security.interfaces.RSAPublicKey;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import java.util.Enumeration;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import javax.servlet.ServletException;
 | 
			
		||||
import javax.servlet.http.Cookie;
 | 
			
		||||
import javax.servlet.http.HttpServletRequest;
 | 
			
		||||
import javax.servlet.http.HttpServletRequestWrapper;
 | 
			
		||||
import javax.servlet.http.HttpServletResponse;
 | 
			
		||||
import javax.servlet.http.HttpSession;
 | 
			
		||||
 | 
			
		||||
import org.apache.commons.codec.binary.Base64;
 | 
			
		||||
import org.apache.commons.lang.StringUtils;
 | 
			
		||||
import org.apache.http.client.HttpClient;
 | 
			
		||||
import org.apache.http.impl.client.DefaultHttpClient;
 | 
			
		||||
import org.mitre.jwt.model.JwtClaims;
 | 
			
		||||
import org.mitre.jwt.signer.JwsAlgorithm;
 | 
			
		||||
import org.mitre.jwt.signer.JwtSigner;
 | 
			
		||||
import org.mitre.jwt.signer.impl.RsaSigner;
 | 
			
		||||
| 
						 | 
				
			
			@ -64,7 +56,6 @@ import org.springframework.util.LinkedMultiValueMap;
 | 
			
		|||
import org.springframework.util.MultiValueMap;
 | 
			
		||||
import org.springframework.web.client.HttpClientErrorException;
 | 
			
		||||
import org.springframework.web.client.RestTemplate;
 | 
			
		||||
import org.springframework.web.util.WebUtils;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.JsonElement;
 | 
			
		||||
import com.google.gson.JsonObject;
 | 
			
		||||
| 
						 | 
				
			
			@ -212,9 +203,7 @@ public class AbstractOIDCAuthenticationFilter extends
 | 
			
		|||
	 * javax.servlet.http.HttpServletResponse)
 | 
			
		||||
	 */
 | 
			
		||||
	@Override
 | 
			
		||||
	public Authentication attemptAuthentication(HttpServletRequest request,
 | 
			
		||||
			HttpServletResponse response) throws AuthenticationException,
 | 
			
		||||
			IOException, ServletException {
 | 
			
		||||
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
 | 
			
		||||
 | 
			
		||||
		logger.debug("Request: "
 | 
			
		||||
				+ request.getRequestURI()
 | 
			
		||||
| 
						 | 
				
			
			@ -237,9 +226,7 @@ public class AbstractOIDCAuthenticationFilter extends
 | 
			
		|||
	 * @throws Exception 
 | 
			
		||||
	 * @throws UnsupportedEncodingException
 | 
			
		||||
	 */
 | 
			
		||||
	protected Authentication handleAuthorizationGrantResponse(
 | 
			
		||||
			String authorizationCode, HttpServletRequest request,
 | 
			
		||||
			OIDCServerConfiguration serverConfig) {
 | 
			
		||||
	protected Authentication handleAuthorizationGrantResponse(String authorizationCode, HttpServletRequest request, OIDCServerConfiguration serverConfig) {
 | 
			
		||||
 | 
			
		||||
		final boolean debug = logger.isDebugEnabled();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -356,58 +343,59 @@ public class AbstractOIDCAuthenticationFilter extends
 | 
			
		|||
				refreshTokenValue = tokenResponse.get("refresh_token").getAsString();
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			JwtSigningAndValidationService jwtValidator = getValidatorForServer(serverConfig); 
 | 
			
		||||
			IdToken idToken = null;
 | 
			
		||||
			
 | 
			
		||||
			try {
 | 
			
		||||
				idToken = IdToken.parse(idTokenValue);
 | 
			
		||||
			
 | 
			
		||||
			} catch (AuthenticationServiceException e) {
 | 
			
		||||
 | 
			
		||||
				// I suspect this could happen
 | 
			
		||||
 | 
			
		||||
				logger.error("Problem parsing id_token:  " + e);
 | 
			
		||||
				// e.printStackTrace();
 | 
			
		||||
 | 
			
		||||
				throw new AuthenticationServiceException("Problem parsing id_token return from Token endpoint: " + e);
 | 
			
		||||
			}
 | 
			
		||||
			IdToken idToken = IdToken.parse(idTokenValue); // TODO: catch parsing errors?
 | 
			
		||||
 | 
			
		||||
			// validate our ID Token over a number of tests
 | 
			
		||||
			JwtClaims idClaims = idToken.getClaims();
 | 
			
		||||
			
 | 
			
		||||
			if(!jwtValidator.validateSignature(idToken.toString())) {
 | 
			
		||||
				throw new AuthenticationServiceException("Signature not validated");
 | 
			
		||||
			// check the signature
 | 
			
		||||
			JwtSigningAndValidationService jwtValidator = getValidatorForServer(serverConfig); 
 | 
			
		||||
			if (jwtValidator != null) {
 | 
			
		||||
				if(!jwtValidator.validateSignature(idToken.toString())) {
 | 
			
		||||
					throw new AuthenticationServiceException("Signature validation failed");
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				logger.info("No validation service found. Skipping signature validation");
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			if(idToken.getClaims().getIssuer() == null) {
 | 
			
		||||
				throw new AuthenticationServiceException("Issuer is null");
 | 
			
		||||
			// check the issuer
 | 
			
		||||
			if (idClaims.getIssuer() == null) {
 | 
			
		||||
				throw new AuthenticationServiceException("Id Token Issuer is null");
 | 
			
		||||
			} else if (!idClaims.getIssuer().equals(serverConfig.getIssuer())){
 | 
			
		||||
				throw new AuthenticationServiceException("Issuers do not match, expected " + serverConfig.getIssuer() + " got " + idClaims.getIssuer());
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			if(!idToken.getClaims().getIssuer().equals(serverConfig.getIssuer())){
 | 
			
		||||
				throw new AuthenticationServiceException("Issuers do not match");
 | 
			
		||||
			// check expiration
 | 
			
		||||
			if (idClaims.getExpiration() == null) {
 | 
			
		||||
				throw new AuthenticationServiceException("Id Token does not have required expiration claim");
 | 
			
		||||
			} else {
 | 
			
		||||
				// it's not null, see if it's expired
 | 
			
		||||
				Date now = new Date();
 | 
			
		||||
				if (!now.after(idClaims.getExpiration())) {
 | 
			
		||||
					throw new AuthenticationServiceException("Id Token is expired: " + idClaims.getExpiration());
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			if(jwtValidator.isJwtExpired(idToken)) {
 | 
			
		||||
				throw new AuthenticationServiceException("Id Token is expired");
 | 
			
		||||
			// check audience
 | 
			
		||||
			if (idClaims.getAudience() == null) {
 | 
			
		||||
				throw new AuthenticationServiceException("Id token audience is null");
 | 
			
		||||
			} else if (!idClaims.getAudience().equals(serverConfig.getClientId())) {
 | 
			
		||||
				throw new AuthenticationServiceException("Audience does not match, expected " + serverConfig.getClientId() + " got " + idClaims.getAudience());
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			if(!jwtValidator.validateIssuedAt(idToken)) {
 | 
			
		||||
				throw new AuthenticationServiceException("Id Token issuedAt failed");
 | 
			
		||||
			// check issued at
 | 
			
		||||
			if (idClaims.getIssuedAt() == null) {
 | 
			
		||||
				throw new AuthenticationServiceException("Id Token does not have required issued-at claim");				
 | 
			
		||||
			} else {
 | 
			
		||||
				// since it's not null, see if it was issued in the future
 | 
			
		||||
				Date now = new Date();
 | 
			
		||||
				if (now.before(idClaims.getIssuedAt())) {
 | 
			
		||||
					throw new AuthenticationServiceException("Id Token was issued in the future: " + idClaims.getIssuedAt());
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Clients are required to compare nonce claim in ID token to 
 | 
			
		||||
			// the nonce sent in the Authorization request.  The client 
 | 
			
		||||
			// stores this value as a signed session cookie to detect a 
 | 
			
		||||
			// replay by third parties.
 | 
			
		||||
			//
 | 
			
		||||
			// See: OpenID Connect Messages Section 2.1.1 entitled "ID Token"
 | 
			
		||||
			//
 | 
			
		||||
			// http://openid.net/specs/openid-connect-messages-1_0.html#id_token
 | 
			
		||||
			//
 | 
			
		||||
			
 | 
			
		||||
			//String nonce = idToken.getClaims().getClaimAsString("nonce");
 | 
			
		||||
 | 
			
		||||
			String nonce = idToken.getClaims().getNonce();
 | 
			
		||||
			
 | 
			
		||||
			// compare the nonce to our stored claim
 | 
			
		||||
			String nonce = idClaims.getNonce();			
 | 
			
		||||
			if (StringUtils.isBlank(nonce)) {
 | 
			
		||||
				
 | 
			
		||||
				logger.error("ID token did not contain a nonce claim.");
 | 
			
		||||
| 
						 | 
				
			
			@ -429,10 +417,9 @@ public class AbstractOIDCAuthenticationFilter extends
 | 
			
		|||
			
 | 
			
		||||
			String userId = idToken.getTokenClaims().getUserId();
 | 
			
		||||
			
 | 
			
		||||
			// construct an OpenIdConnectAuthenticationToken and return 
 | 
			
		||||
			// a Authentication object w/the userId and the idToken
 | 
			
		||||
			// construct an OpenIdConnectAuthenticationToken and return a Authentication object w/the userId and the idToken
 | 
			
		||||
			
 | 
			
		||||
			OpenIdConnectAuthenticationToken token = new OpenIdConnectAuthenticationToken(userId, idToken.getClaims().getIssuer(), serverConfig, idTokenValue, accessTokenValue, refreshTokenValue);
 | 
			
		||||
			OpenIdConnectAuthenticationToken token = new OpenIdConnectAuthenticationToken(userId, idClaims.getIssuer(), serverConfig, idTokenValue, accessTokenValue, refreshTokenValue);
 | 
			
		||||
 | 
			
		||||
			Authentication authentication = this.getAuthenticationManager().authenticate(token);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -615,13 +602,15 @@ public class AbstractOIDCAuthenticationFilter extends
 | 
			
		|||
			KeyFetcher keyFetch = new KeyFetcher();
 | 
			
		||||
			PublicKey signingKey = null;
 | 
			
		||||
			
 | 
			
		||||
			// If the config has the X509 url, prefer that to the JWK
 | 
			
		||||
			if(serverConfig.getX509SigningUrl() != null) {
 | 
			
		||||
				signingKey = keyFetch.retrieveX509Key(serverConfig);
 | 
			
		||||
				
 | 
			
		||||
			} else {
 | 
			
		||||
				// otherwise prefer the JWK
 | 
			
		||||
			if (serverConfig.getJwkSigningUrl() != null) {
 | 
			
		||||
				// prefer the JWK
 | 
			
		||||
				signingKey = keyFetch.retrieveJwkKey(serverConfig);
 | 
			
		||||
			} else if (serverConfig.getX509SigningUrl() != null) {
 | 
			
		||||
				// use the x509 only if JWK isn't configured
 | 
			
		||||
				signingKey = keyFetch.retrieveX509Key(serverConfig);				
 | 
			
		||||
			} else {
 | 
			
		||||
				// no keys configured
 | 
			
		||||
				logger.warn("No server key URLs configured for " + serverConfig.getIssuer());
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
			if (signingKey != null) {
 | 
			
		||||
| 
						 | 
				
			
			@ -648,6 +637,7 @@ public class AbstractOIDCAuthenticationFilter extends
 | 
			
		|||
				return signingAndValidationService;
 | 
			
		||||
				
 | 
			
		||||
			} else {
 | 
			
		||||
				// there were either no keys returned or no URLs configured to fetch them, assume no checking on key signatures
 | 
			
		||||
				return null;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,17 +30,6 @@ public interface JwtSigningAndValidationService {
 | 
			
		|||
	 */
 | 
			
		||||
	public Map<String, JwtSigner> getAllSigners();
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Check to see if this JWT has expired or not
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param jwt
 | 
			
		||||
	 *            the JWT to check
 | 
			
		||||
	 * @return true if this JWT has an expiration and it has passed, false if
 | 
			
		||||
	 *         the JWT has no expiration or it has an expiration and the
 | 
			
		||||
	 *         expiration has not passed
 | 
			
		||||
	 */
 | 
			
		||||
	public boolean isJwtExpired(Jwt jwt);
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Checks the signature of the given JWT against all configured signers,
 | 
			
		||||
	 * returns true if at least one of the signers validates it.
 | 
			
		||||
| 
						 | 
				
			
			@ -53,36 +42,6 @@ public interface JwtSigningAndValidationService {
 | 
			
		|||
	public boolean validateSignature(String jwtString);
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Checks to see when this JWT was issued
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param jwt
 | 
			
		||||
	 * 		the JWT to check
 | 
			
		||||
	 * @return true if the issued at is valid, false if not
 | 
			
		||||
	 * @throws NoSuchAlgorithmException
 | 
			
		||||
	 */
 | 
			
		||||
	public boolean validateIssuedAt(Jwt jwt);
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * Checks to see if the nonce parameter sent in the Authorization Request 
 | 
			
		||||
	 * is equal to the nonce parameter in the id token
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param jwt
 | 
			
		||||
	 * @param nonce
 | 
			
		||||
	 * 			the string representation of the Nonce
 | 
			
		||||
	 * @return true if both nonce parameters are equal, false if otherwise 
 | 
			
		||||
	 * @throws NoSuchAlgorithmException
 | 
			
		||||
	 */
 | 
			
		||||
	public boolean validateNonce(Jwt jwt, String nonce);
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * Sign a jwt using the selected algorithm. The algorithm is selected using the String parameter values specified
 | 
			
		||||
	 * in the JWT spec, section 6. I.E., "HS256" means HMAC with SHA-256 and corresponds to our HmacSigner class.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param jwt the jwt to sign
 | 
			
		||||
	 * @param alg the name of the algorithm to use, as specified in JWS s.6
 | 
			
		||||
	 * @return the signed jwt
 | 
			
		||||
	 */
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * Called to sign a jwt in place for a client that hasn't registered a preferred signing algorithm.
 | 
			
		||||
| 
						 | 
				
			
			@ -94,6 +53,14 @@ public interface JwtSigningAndValidationService {
 | 
			
		|||
	 */
 | 
			
		||||
	public void signJwt(Jwt jwt) throws NoSuchAlgorithmException;
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * Sign a jwt using the selected algorithm. The algorithm is selected using the String parameter values specified
 | 
			
		||||
	 * in the JWT spec, section 6. I.E., "HS256" means HMAC with SHA-256 and corresponds to our HmacSigner class.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param jwt the jwt to sign
 | 
			
		||||
	 * @param alg the name of the algorithm to use, as specified in JWS s.6
 | 
			
		||||
	 * @return the signed jwt
 | 
			
		||||
	 */
 | 
			
		||||
	//TODO: implement later; only need signJwt(Jwt jwt) for now
 | 
			
		||||
	//public Jwt signJwt(Jwt jwt, String alg);
 | 
			
		||||
	
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,69 +0,0 @@
 | 
			
		|||
package org.mitre.jwt.signer.service.impl;
 | 
			
		||||
 | 
			
		||||
import java.security.NoSuchAlgorithmException;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import org.mitre.jwt.model.Jwt;
 | 
			
		||||
import org.mitre.jwt.signer.JwtSigner;
 | 
			
		||||
import org.mitre.jwt.signer.service.JwtSigningAndValidationService;
 | 
			
		||||
 | 
			
		||||
public abstract class AbstractJwtSigningAndValidationService implements JwtSigningAndValidationService{
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Return the JwtSigners associated with this service
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @return
 | 
			
		||||
	 */
 | 
			
		||||
	public abstract Map<String, ? extends JwtSigner> getSigners(); 
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean isJwtExpired(Jwt jwt) {
 | 
			
		||||
	
 | 
			
		||||
		Date expiration = jwt.getClaims().getExpiration();
 | 
			
		||||
	
 | 
			
		||||
		if (expiration != null) {
 | 
			
		||||
			return new Date().after(expiration);
 | 
			
		||||
		} else {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean validateSignature(String jwtString) {
 | 
			
		||||
	
 | 
			
		||||
		for (JwtSigner signer : getSigners().values()) {
 | 
			
		||||
			try {
 | 
			
		||||
	            if (signer.verify(jwtString)) {
 | 
			
		||||
	            	return true;
 | 
			
		||||
	            }
 | 
			
		||||
            } catch (NoSuchAlgorithmException e) {
 | 
			
		||||
            	// ignore, signer didn't verify signature, try the next one
 | 
			
		||||
            	e.printStackTrace();
 | 
			
		||||
            }
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean validateIssuedAt(Jwt jwt) {
 | 
			
		||||
		Date issuedAt = jwt.getClaims().getIssuedAt();
 | 
			
		||||
		
 | 
			
		||||
		if (issuedAt != null) {
 | 
			
		||||
			// make sure the token was issued in the past
 | 
			
		||||
			return new Date().after(issuedAt);
 | 
			
		||||
		} else {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean validateNonce(Jwt jwt, String nonce) {
 | 
			
		||||
		if(jwt.getClaims().getNonce().equals(nonce)){
 | 
			
		||||
			return true;
 | 
			
		||||
		} else {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -28,8 +28,7 @@ import org.slf4j.LoggerFactory;
 | 
			
		|||
import org.springframework.beans.factory.InitializingBean;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
 | 
			
		||||
public class JwtSigningAndValidationServiceDefault extends AbstractJwtSigningAndValidationService implements
 | 
			
		||||
		JwtSigningAndValidationService, InitializingBean {
 | 
			
		||||
public class JwtSigningAndValidationServiceDefault implements JwtSigningAndValidationService, InitializingBean {
 | 
			
		||||
 | 
			
		||||
	@Autowired 
 | 
			
		||||
	private ConfigurationPropertiesBean configBean;
 | 
			
		||||
| 
						 | 
				
			
			@ -51,8 +50,7 @@ public class JwtSigningAndValidationServiceDefault extends AbstractJwtSigningAnd
 | 
			
		|||
	 * @param signer
 | 
			
		||||
	 *            List of JwtSigners to associate with this service
 | 
			
		||||
	 */
 | 
			
		||||
	public JwtSigningAndValidationServiceDefault(
 | 
			
		||||
			Map<String, ? extends JwtSigner> signer) {
 | 
			
		||||
	public JwtSigningAndValidationServiceDefault(Map<String, ? extends JwtSigner> signer) {
 | 
			
		||||
		setSigners(signer);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
| 
						 | 
				
			
			@ -106,8 +104,7 @@ public class JwtSigningAndValidationServiceDefault extends AbstractJwtSigningAnd
 | 
			
		|||
	 */
 | 
			
		||||
	@Override
 | 
			
		||||
	public String toString() {
 | 
			
		||||
		return "JwtSigningAndValidationServiceDefault [signers=" + signers
 | 
			
		||||
				+ "]";
 | 
			
		||||
		return "JwtSigningAndValidationServiceDefault [signers=" + signers + "]";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
| 
						 | 
				
			
			@ -150,4 +147,20 @@ public class JwtSigningAndValidationServiceDefault extends AbstractJwtSigningAnd
 | 
			
		|||
		return signers;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean validateSignature(String jwtString) {
 | 
			
		||||
 | 
			
		||||
		for (JwtSigner signer : getSigners().values()) {
 | 
			
		||||
			try {
 | 
			
		||||
				if (signer.verify(jwtString)) {
 | 
			
		||||
					return true;
 | 
			
		||||
				}
 | 
			
		||||
			} catch (NoSuchAlgorithmException e) {
 | 
			
		||||
				// ignore, signer didn't verify signature, try the next one
 | 
			
		||||
				e.printStackTrace();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue