com.nimbusds.oauth2.sdk.token.TokenSchemeError Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of oauth2-oidc-sdk Show documentation
Show all versions of oauth2-oidc-sdk Show documentation
OAuth 2.0 SDK with OpenID Connection extensions for developing client
and server applications.
/*
* oauth2-oidc-sdk
*
* Copyright 2012-2021, Connect2id Ltd and contributors.
*
* 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 com.nimbusds.oauth2.sdk.token;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.nimbusds.oauth2.sdk.ErrorObject;
import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.oauth2.sdk.Scope;
/**
* The base abstract class for token scheme errors. Concrete extending classes
* should be immutable.
*/
public abstract class TokenSchemeError extends ErrorObject {
private static final long serialVersionUID = -1132784406578139418L;
/**
* Regex pattern for matching the realm parameter of a WWW-Authenticate
* header. Limits the realm string length to 256 chars to prevent
* potential stack overflow exception for very long strings due to
* recursive nature of regex.
*/
static final Pattern REALM_PATTERN = Pattern.compile("realm=\"(([^\\\\\"]|\\\\.){0,256})\"");
/**
* Regex pattern for matching the error parameter of a WWW-Authenticate
* header. Double quoting is optional.
*/
static final Pattern ERROR_PATTERN = Pattern.compile("error=(\"([\\w\\_-]+)\"|([\\w\\_-]+))");
/**
* Regex pattern for matching the error description parameter of a
* WWW-Authenticate header.
*/
static final Pattern ERROR_DESCRIPTION_PATTERN = Pattern.compile("error_description=\"([^\"]+)\"");
/**
* Regex pattern for matching the error URI parameter of a
* WWW-Authenticate header.
*/
static final Pattern ERROR_URI_PATTERN = Pattern.compile("error_uri=\"([^\"]+)\"");
/**
* Regex pattern for matching the scope parameter of a WWW-Authenticate
* header.
*/
static final Pattern SCOPE_PATTERN = Pattern.compile("scope=\"([^\"]+)");
/**
* The token scheme.
*/
private final AccessTokenType scheme;
/**
* The realm, {@code null} if not specified.
*/
private final String realm;
/**
* Required scope, {@code null} if not specified.
*/
private final Scope scope;
/**
* Returns {@code true} if the specified scope consists of valid
* characters. Values for the "scope" attributes must not include
* characters outside the [0x20, 0x21] | [0x23 - 0x5B] | [0x5D - 0x7E]
* range. See RFC 6750, section 3.
*
* @see ErrorObject#isLegal(String)
*
* @param scope The scope.
*
* @return {@code true} if the scope contains valid characters, else
* {@code false}.
*/
public static boolean isScopeWithValidChars(final Scope scope) {
return ErrorObject.isLegal(scope.toString());
}
/**
* Creates a new token error with the specified code, description, HTTP
* status code, page URI, realm and scope.
*
* @param scheme The token scheme. Must not be {@code null}.
* @param code The error code, {@code null} if not specified.
* @param description The error description, {@code null} if not
* specified.
* @param httpStatusCode The HTTP status code, zero if not specified.
* @param uri The error page URI, {@code null} if not
* specified.
* @param realm The realm, {@code null} if not specified.
* @param scope The required scope, {@code null} if not
* specified.
*/
protected TokenSchemeError(final AccessTokenType scheme,
final String code,
final String description,
final int httpStatusCode,
final URI uri,
final String realm,
final Scope scope) {
super(code, description, httpStatusCode, uri);
if (scheme == null) {
throw new IllegalArgumentException("The token scheme must not be null");
}
this.scheme = scheme;
this.realm = realm;
this.scope = scope;
if (scope != null && ! isScopeWithValidChars(scope)) {
throw new IllegalArgumentException("The scope contains illegal characters, see RFC 6750, section 3");
}
}
/**
* Returns the token scheme.
*
* @return The token scheme.
*/
public AccessTokenType getScheme() {
return scheme;
}
/**
* Returns the realm.
*
* @return The realm, {@code null} if not specified.
*/
public String getRealm() {
return realm;
}
/**
* Returns the required scope.
*
* @return The required scope, {@code null} if not specified.
*/
public Scope getScope() {
return scope;
}
@Override
public abstract TokenSchemeError setDescription(final String description);
@Override
public abstract TokenSchemeError appendDescription(final String text);
@Override
public abstract TokenSchemeError setHTTPStatusCode(final int httpStatusCode);
@Override
public abstract TokenSchemeError setURI(final URI uri);
/**
* Sets the realm.
*
* @param realm realm, {@code null} if not specified.
*
* @return A copy of this error with the specified realm.
*/
public abstract TokenSchemeError setRealm(final String realm);
/**
* Sets the required scope.
*
* @param scope The required scope, {@code null} if not specified.
*
* @return A copy of this error with the specified required scope.
*/
public abstract TokenSchemeError setScope(final Scope scope);
/**
* Returns the {@code WWW-Authenticate} HTTP response header code for
* this token scheme error.
*
* Example:
*
*
* Bearer realm="example.com", error="invalid_token", error_description="Invalid access token"
*
*
* @return The {@code Www-Authenticate} header value.
*/
public String toWWWAuthenticateHeader() {
StringBuilder sb = new StringBuilder(getScheme().getValue());
int numParams = 0;
// Serialise realm, may contain double quotes
if (getRealm() != null) {
sb.append(" realm=\"");
sb.append(getRealm().replaceAll("\"","\\\\\""));
sb.append('"');
numParams++;
}
// Serialise error, error_description, error_uri
if (getCode() != null) {
if (numParams > 0)
sb.append(',');
sb.append(" error=\"");
sb.append(getCode());
sb.append('"');
numParams++;
if (getDescription() != null) {
// Output description only if code is present
sb.append(',');
sb.append(" error_description=\"");
sb.append(getDescription());
sb.append('"');
numParams++;
}
if (getURI() != null) {
// Output description only if code is present
sb.append(',');
sb.append(" error_uri=\"");
sb.append(getURI().toString()); // double quotes always escaped in URI representation
sb.append('"');
numParams++;
}
}
// Serialise scope
if (getScope() != null) {
if (numParams > 0)
sb.append(',');
sb.append(" scope=\"");
sb.append(getScope().toString());
sb.append('"');
}
return sb.toString();
}
/**
* Parses an OAuth 2.0 generic token scheme error from the specified
* HTTP response {@code WWW-Authenticate} header.
*
* @param wwwAuth The {@code WWW-Authenticate} header value to parse.
* Must not be {@code null}.
* @param scheme The token scheme. Must not be {@code null}.
*
* @return The generic token scheme error.
*
* @throws ParseException If the {@code WWW-Authenticate} header value
* couldn't be parsed to a generic token scheme
* error.
*/
static TokenSchemeError parse(final String wwwAuth,
final AccessTokenType scheme)
throws ParseException {
// We must have a WWW-Authenticate header set to .*
if (! wwwAuth.regionMatches(true, 0, scheme.getValue(), 0, scheme.getValue().length()))
throw new ParseException("WWW-Authenticate scheme must be OAuth 2.0 DPoP");
Matcher m;
// Parse optional realm
m = REALM_PATTERN.matcher(wwwAuth);
String realm = null;
if (m.find())
realm = m.group(1);
if (realm != null)
realm = realm.replace("\\\"", "\""); // strip escaped double quotes
// Parse optional error
String errorCode = null;
String errorDescription = null;
URI errorURI = null;
m = ERROR_PATTERN.matcher(wwwAuth);
if (m.find()) {
// Error code: try group with double quotes, else group with no quotes
errorCode = m.group(2) != null ? m.group(2) : m.group(3);
if (! ErrorObject.isLegal(errorCode))
errorCode = null; // found invalid chars
// Parse optional error description
m = ERROR_DESCRIPTION_PATTERN.matcher(wwwAuth);
if (m.find())
errorDescription = m.group(1);
// Parse optional error URI
m = ERROR_URI_PATTERN.matcher(wwwAuth);
if (m.find()) {
try {
errorURI = new URI(m.group(1));
} catch (URISyntaxException e) {
// ignore, URI is not required to construct error object
}
}
}
Scope scope = null;
m = SCOPE_PATTERN.matcher(wwwAuth);
if (m.find())
scope = Scope.parse(m.group(1));
return new TokenSchemeError(AccessTokenType.UNKNOWN, errorCode, errorDescription, 0, errorURI, realm, scope) {
private static final long serialVersionUID = -1629382220440634919L;
@Override
public TokenSchemeError setDescription(String description) {
return null;
}
@Override
public TokenSchemeError appendDescription(String text) {
return null;
}
@Override
public TokenSchemeError setHTTPStatusCode(int httpStatusCode) {
return null;
}
@Override
public TokenSchemeError setURI(URI uri) {
return null;
}
@Override
public TokenSchemeError setRealm(String realm) {
return null;
}
@Override
public TokenSchemeError setScope(Scope scope) {
return null;
}
};
}
}