oidc-library/src/main/java/org/gcube/oidc/rest/OpenIdConnectRESTHelper.java

599 lines
29 KiB
Java

package org.gcube.oidc.rest;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OpenIdConnectRESTHelper {
protected static final Logger logger = LoggerFactory.getLogger(OpenIdConnectRESTHelper.class);
private static final String RESPONSE_ERROR_KEY = "error";
private static final String RESPONSE_ERROR_INVALID_GRANT = "invalid_grant";
private static final String RESPONSE_ERROR_ACCESS_DENIED = "access_denied";
private static final String RESPONSE_ERROR_DESCRIPTION_KEY = "error_description";
private static final String RESPONSE_ERROR_DESCRIPTION_TINA = "Token is not active";
private static final String RESPONSE_ERROR_DESCRIPTION_IBT = "Invalid bearer token";
private static final String RESPONSE_ERROR_DESCRIPTION_NOT_AUTHORIZED = "not_authorized";
public static String buildLoginRequestURL(URL loginURL, String clientId, String state, String redirectURI)
throws UnsupportedEncodingException {
Map<String, List<String>> params = new HashMap<String, List<String>>();
params.put("client_id", Arrays.asList(URLEncoder.encode(clientId, "UTF-8")));
params.put("response_type", Arrays.asList("code"));
params.put("scope", Arrays.asList("openid"));
params.put("state", Arrays.asList(URLEncoder.encode(state, "UTF-8")));
params.put("redirect_uri", Arrays.asList(URLEncoder.encode(redirectURI, "UTF-8")));
params.put("login", Arrays.asList("true"));
String q = mapToQueryString(params);
return loginURL + "?" + q;
}
public static String mapToQueryString(Map<String, List<String>> params) {
String q = params.entrySet().stream().flatMap(p -> p.getValue().stream().map(v -> p.getKey() + "=" + v))
.reduce((p1, p2) -> p1 + "&" + p2).orElse("");
logger.debug("Query string is: {}", q);
return q;
}
/**
* Queries from the OIDC server an OIDC access token, by using provided clientId and client secret.
*
* @param clientId the client id
* @param clientSecret the client secret
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @return the issued token
* @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details
*/
public static JWTToken queryClientToken(String clientId, String clientSecret, URL tokenURL)
throws OpenIdConnectRESTHelperException {
return queryClientToken(clientId, clientSecret, tokenURL, null);
}
/**
* Queries from the OIDC server an OIDC access token, by using provided clientId and client secret.
*
* @param clientId the client id
* @param clientSecret the client secret
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @param extraHeaders extra HTTP headers to add to the request (e.g. <code>X-D4Science-Context</code> custom header), may be <code>null</code>
* @return the issued token
* @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details
*/
public static JWTToken queryClientToken(String clientId, String clientSecret, URL tokenURL,
Map<String, String> extraHeaders)
throws OpenIdConnectRESTHelperException {
Map<String, List<String>> params = new HashMap<>();
params.put("grant_type", Arrays.asList("client_credentials"));
try {
params.put("client_id", Arrays.asList(URLEncoder.encode(clientId, "UTF-8")));
} catch (UnsupportedEncodingException e) {
logger.error("Cannot URL encode 'client_id'", e);
}
try {
params.put("client_secret", Arrays.asList(URLEncoder.encode(clientSecret, "UTF-8")));
} catch (UnsupportedEncodingException e) {
logger.error("Cannot URL encode 'client_secret'", e);
}
return performQueryTokenWithPOST(tokenURL, null, params, extraHeaders);
}
public static JWTToken queryToken(String clientId, URL tokenURL, String code, String scope,
String redirectURI) throws Exception {
return queryToken(clientId, tokenURL, code, scope, redirectURI, null);
}
public static JWTToken queryToken(String clientId, URL tokenURL, String code, String scope,
String redirectURI, Map<String, String> extraHeaders) throws Exception {
Map<String, List<String>> params = new HashMap<>();
params.put("client_id", Arrays.asList(URLEncoder.encode(clientId, "UTF-8")));
params.put("grant_type", Arrays.asList("authorization_code"));
params.put("scope", Arrays.asList(URLEncoder.encode(scope, "UTF-8")));
params.put("code", Arrays.asList(URLEncoder.encode(code, "UTF-8")));
params.put("redirect_uri", Arrays.asList(URLEncoder.encode(redirectURI, "UTF-8")));
return performQueryTokenWithPOST(tokenURL, null, params, extraHeaders);
}
protected static JWTToken performQueryTokenWithPOST(URL tokenURL, String authorization,
Map<String, List<String>> params) throws OpenIdConnectRESTHelperException {
return performQueryTokenWithPOST(tokenURL, authorization, params, null);
}
protected static JWTToken performQueryTokenWithPOST(URL tokenURL, String authorization,
Map<String, List<String>> params, Map<String, String> headers) throws OpenIdConnectRESTHelperException {
logger.debug("Querying access token from OIDC server with URL: {}", tokenURL);
StringBuilder sb;
try {
HttpURLConnection httpURLConnection = performURLEncodedPOSTSendData(tokenURL, params, authorization,
headers);
sb = new StringBuilder();
int httpResultCode = httpURLConnection.getResponseCode();
logger.trace("HTTP Response code: {}", httpResultCode);
String responseContentType = httpURLConnection.getContentType();
if (responseContentType != null) {
logger.debug("Response content type is: {}", responseContentType);
} else {
responseContentType = "";
}
if (httpResultCode != HttpURLConnection.HTTP_OK) {
BufferedReader br = new BufferedReader(
new InputStreamReader(httpURLConnection.getErrorStream(), "UTF-8"));
String line = null;
while ((line = br.readLine()) != null) {
sb.append(line + "\n");
}
br.close();
throw OpenIdConnectRESTHelperException.create("Unable to get token", httpResultCode,
responseContentType, sb.toString());
} else {
BufferedReader br = new BufferedReader(
new InputStreamReader(httpURLConnection.getInputStream(), "UTF-8"));
String line = null;
while ((line = br.readLine()) != null) {
sb.append(line + "\n");
}
br.close();
}
} catch (IOException e) {
throw new OpenIdConnectRESTHelperException("Unable to get the token", e);
}
return JWTToken.fromString(sb.toString());
}
protected static HttpURLConnection performURLEncodedPOSTSendData(URL url, Map<String, List<String>> params,
String authorization) throws IOException, ProtocolException, UnsupportedEncodingException {
return performURLEncodedPOSTSendData(url, params, authorization, null);
}
protected static HttpURLConnection performURLEncodedPOSTSendData(URL url, Map<String, List<String>> params,
String authorization, Map<String, String> headers)
throws IOException, ProtocolException, UnsupportedEncodingException {
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setDoOutput(true);
con.setDoInput(true);
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
con.setRequestProperty("Accept", "application/json");
if (authorization != null) {
logger.debug("Adding authorization header as: {}", authorization);
con.setRequestProperty("Authorization", authorization);
}
if (headers != null) {
for (String key : headers.keySet()) {
con.setRequestProperty(key, headers.get(key));
}
}
OutputStream os = con.getOutputStream();
String queryString = mapToQueryString(params);
logger.debug("Parameters query string is: {}", queryString);
os.write(queryString.getBytes("UTF-8"));
os.close();
return con;
}
/**
* Queries from the OIDC server an UMA token, by using provided clientId and client secret for the given audience
* (context), in URLEncoded form or not, and optionally a list of permissions.
*
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @param clientId the client id
* @param clientSecret the client secret
* @param audience the audience (context) where to request the issuing of the token (URLEncoded or not)
* @param permissions a list of permissions, can be <code>null</code>
* @return the issued token
* @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details
*/
public static JWTToken queryUMAToken(URL tokenUrl, String clientId, String clientSecret, String audience,
List<String> permissions) throws OpenIdConnectRESTHelperException {
return queryUMAToken(tokenUrl, clientId, clientSecret, audience, permissions, null);
}
/**
* Queries from the OIDC server an UMA token, by using provided clientId and client secret for the given audience
* (context), in URLEncoded form or not, and optionally a list of permissions.
*
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @param clientId the client id
* @param clientSecret the client secret
* @param audience the audience (context) where to request the issuing of the token (URLEncoded or not)
* @param permissions a list of permissions, can be <code>null</code>
* @param extraHeaders extra HTTP headers to add to the request (e.g. <code>X-D4Science-Context</code> custom header), may be <code>null</code>
* @return the issued token
* @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details
*/
public static JWTToken queryUMAToken(URL tokenUrl, String clientId, String clientSecret, String audience,
List<String> permissions, Map<String, String> extraHeaders) throws OpenIdConnectRESTHelperException {
return queryUMAToken(tokenUrl,
"Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes()),
audience, permissions, extraHeaders);
}
/**
* Queries from the OIDC server an UMA token, by using provided access token, for the given audience (context),
* in URLEncoded form or not, and optionally a list of permissions.
*
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @param authorization the auth token (the access token URLEncoded by the "Bearer " string)
* @param audience the audience (context) where to request the issuing of the token (URLEncoded or not)
* @param permissions a list of permissions, can be <code>null</code>
* @return the issued token
* @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details
*/
public static JWTToken queryUMAToken(URL tokenUrl, String authorization, String audience,
List<String> permissions) throws OpenIdConnectRESTHelperException {
return queryUMAToken(tokenUrl, authorization, audience, permissions, null);
}
/**
* Queries from the OIDC server an UMA token, by using provided access token, for the given audience (context),
* in URLEncoded form or not, and optionally a list of permissions.
*
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @param authorization the auth token (the access token URLEncoded by the "Bearer " string)
* @param audience the audience (context) where to request the issuing of the token (URLEncoded or not)
* @param permissions a list of permissions, can be <code>null</code>
* @param extraHeaders extra HTTP headers to add to the request (e.g. <code>X-D4Science-Context</code> custom header), may be <code>null</code>
* @return the issued token
* @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details
*/
public static JWTToken queryUMAToken(URL tokenUrl, String authorization, String audience,
List<String> permissions, Map<String, String> extraHeaders) throws OpenIdConnectRESTHelperException {
Map<String, List<String>> params = new HashMap<>();
params.put("grant_type", Arrays.asList("urn:ietf:params:oauth:grant-type:uma-ticket"));
if (audience.startsWith("/")) {
try {
logger.trace("Audience was provided in non URL encoded form, encoding it");
audience = URLEncoder.encode(audience, "UTF-8");
} catch (UnsupportedEncodingException e) {
logger.error("Cannot URL encode 'audience'", e);
}
}
try {
params.put("audience", Arrays.asList(URLEncoder.encode(audience, "UTF-8")));
} catch (UnsupportedEncodingException e) {
logger.error("Cannot URL encode 'audience'", e);
}
if (permissions != null && !permissions.isEmpty()) {
params.put(
"permission", permissions.stream().map(s -> {
try {
return URLEncoder.encode(s, "UTF-8");
} catch (UnsupportedEncodingException e) {
return "";
}
}).collect(Collectors.toList()));
}
return performQueryTokenWithPOST(tokenUrl, authorization, params, extraHeaders);
}
/**
* Queries from the OIDC server an exchanged token by using provided access token, optionally for the given audience (context)
* in URLEncoded form or not.
*
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @param authorization the auth token (the access token URLEncoded by the "Bearer " string)
* @param audience the audience (context) where to request the issuing of the token (URLEncoded or not), may be <code>null</code>
* @param clientId the client id
* @param clientSecret the client secret
* @param extraHeaders extra HTTP headers to add to the request (e.g. <code>X-D4Science-Context</code> custom header), may be <code>null</code>
* @return the issued token
* @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details
*/
public static JWTToken queryExchangeToken(URL tokenUrl, String authorization, String audience, String client_id,
String client_secret, Map<String, String> extraHeaders) throws OpenIdConnectRESTHelperException {
return queryExchangeToken(tokenUrl, authorization, audience, client_id, client_secret,
"urn:ietf:params:oauth:token-type:access_token", null, extraHeaders);
}
/**
* Queries from the OIDC server an exchanged token by using provided access token, optionally for the given audience (context)
* in URLEncoded form or not.
*
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @param authorization the auth token (the access token URLEncoded by the "Bearer " string)
* @param audience the audience (context) where to request the issuing of the token (URLEncoded or not), may be <code>null</code>
* @param clientId the client id
* @param clientSecret the client secret
* @param withRefreshToken request also the refresh token (forced to <code>true</code> for offline requests)
* @param offline request a refresh token of offline type (TYP claim)
* @param extraHeaders extra HTTP headers to add to the request (e.g. <code>X-D4Science-Context</code> custom header), may be <code>null</code>
* @return the issued token
* @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details
*/
public static JWTToken queryExchangeToken(URL tokenUrl, String authorization, String audience, String clientId,
String clientSecret, boolean withRefreshToken, boolean offline, Map<String, String> extraHeaders)
throws OpenIdConnectRESTHelperException {
return queryExchangeToken(tokenUrl, authorization, audience, clientId, clientSecret,
withRefreshToken || offline ? "urn:ietf:params:oauth:token-type:refresh_token"
: "urn:ietf:params:oauth:token-type:access_token",
offline ? "offline_access" : null, extraHeaders);
}
/**
* Queries from the OIDC server an exchanged token by using provided access token, optionally for the given audience (context)
* in URLEncoded form or not.
*
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @param authorization the auth token (the access token URLEncoded by the "Bearer " string)
* @param audience the audience (context) where to request the issuing of the token (URLEncoded or not), may be <code>null</code>
* @param clientId the client id
* @param clientSecret the client secret
* @param requestedTokenType the requested token type (e.g. <code>urn:ietf:params:oauth:token-type:refresh_token</code> for refresh token)
* @param scope the optional scope to request (e.g. <code>offline_access</code> for an offline token)
* @param extraHeaders extra HTTP headers to add to the request (e.g. <code>X-D4Science-Context</code> custom header), may be <code>null</code>
* @return the issued token
* @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details
*/
public static JWTToken queryExchangeToken(URL tokenUrl, String authorization, String audience, String clientId,
String clientSecret, String requestedTokenType, String scope, Map<String, String> extraHeaders)
throws OpenIdConnectRESTHelperException {
logger.info("Querying exchange token for context: " + audience);
Map<String, List<String>> params = new HashMap<>();
params.put("subject_token", Arrays.asList(authorization));
params.put("client_id", Arrays.asList(clientId));
params.put("client_secret", Arrays.asList(clientSecret));
params.put("grant_type", Arrays.asList("urn:ietf:params:oauth:grant-type:token-exchange"));
params.put("subject_token_type", Arrays.asList("urn:ietf:params:oauth:token-type:access_token"));
params.put("requested_token_type", Arrays.asList(requestedTokenType));
if (scope != null) {
params.put("scope", Arrays.asList(scope));
}
if (audience != null) {
if (audience.startsWith("/")) {
try {
logger.trace("Audience was provided in non URL encoded form, encoding it");
audience = URLEncoder.encode(audience, "UTF-8");
} catch (UnsupportedEncodingException e) {
logger.error("Cannot URL encode 'audience'", e);
}
}
try {
params.put("audience", Arrays.asList(URLEncoder.encode(audience, "UTF-8")));
} catch (UnsupportedEncodingException e) {
logger.error("Cannot URL encode 'audience'", e);
}
}
return performQueryTokenWithPOST(tokenUrl, null, params, extraHeaders);
}
/**
* Refreshes the token from the OIDC server.
*
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @param token the token to be refreshed
* @return a new token refreshed from the previous one
* @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details
*/
public static JWTToken refreshToken(URL tokenURL, JWTToken token) throws OpenIdConnectRESTHelperException {
return refreshToken(tokenURL, null, null, token);
}
/**
* Refreshes the token from the OIDC server for a specific client represented by the client id.
*
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @param clientId the client id
* @param token the token to be refreshed
* @return a new token refreshed from the previous one
* @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details
*/
public static JWTToken refreshToken(URL tokenURL, String clientId, JWTToken token)
throws OpenIdConnectRESTHelperException {
return refreshToken(tokenURL, clientId, null, token);
}
/**
* Refreshes the token from the OIDC server for a specific client represented by the client id.
*
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @param clientId the client id
* @param clientSecret the client secret
* @param token the token to be refreshed
* @return a new token refreshed from the previous one
* @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details
*/
public static JWTToken refreshToken(URL tokenURL, String clientId, String clientSecret, JWTToken token)
throws OpenIdConnectRESTHelperException {
Map<String, List<String>> params = new HashMap<>();
params.put("grant_type", Arrays.asList("refresh_token"));
if (clientId == null) {
clientId = getClientIdFromToken(token);
}
try {
params.put("client_id", Arrays.asList(URLEncoder.encode(clientId, "UTF-8")));
} catch (UnsupportedEncodingException e) {
logger.error("Cannot URL encode 'client_id'", e);
}
if (clientSecret != null) {
try {
params.put("client_secret", Arrays.asList(URLEncoder.encode(clientSecret, "UTF-8")));
} catch (UnsupportedEncodingException e) {
logger.error("Cannot URL encode 'client_secret'", e);
}
}
params.put("refresh_token", Arrays.asList(token.getRefreshTokenString()));
return performQueryTokenWithPOST(tokenURL, null, params);
}
protected static String getClientIdFromToken(JWTToken token) {
String clientId;
logger.debug("Client id not provided, using authorized party field (azp)");
clientId = token.getAzp();
if (clientId == null) {
logger.debug("Authorized party field (azp) not present, getting one of the audience field (aud)");
clientId = getFirstAudienceNoAccount(token);
}
return clientId;
}
private static String getFirstAudienceNoAccount(JWTToken token) {
// Trying to get it from the token's audience ('aud' field), getting the first except the 'account'
List<String> tokenAud = token.getAud();
tokenAud.remove(JWTToken.ACCOUNT_RESOURCE);
if (tokenAud.size() > 0) {
return tokenAud.iterator().next();
} else {
// Setting it to empty string to avoid NPE in encoding
return "";
}
}
/**
* Performs the logout (SSOut) from all the sessions opened in the OIDC server.
*
* @param logoutUrl the logut endpoint {@link URL} of the OIDC server
* @param token the token used to take info from
* @return <code>true</code> if the logout is performed correctly, <code>false</code> otherwise
* @throws IOException if an I/O error occurs during the communication with the server
*/
public static boolean logout(URL logoutUrl, JWTToken token) throws IOException {
return logout(logoutUrl, null, token);
}
/**
* Performs the logout from the session related to the provided client id in the OIDC server.
*
* @param logoutUrl the logut endpoint {@link URL} of the OIDC server
* @param clientId the client id
* @param token the token used to take info from
* @return <code>true</code> if the logout is performed correctly, <code>false</code> otherwise
* @throws IOException if an I/O error occurs during the communication with the server
*/
public static boolean logout(URL logoutUrl, String clientId, JWTToken token) throws IOException {
Map<String, List<String>> params = new HashMap<>();
if (clientId == null) {
clientId = getClientIdFromToken(token);
}
params.put("client_id", Arrays.asList(URLEncoder.encode(clientId, "UTF-8")));
params.put("refresh_token", Arrays.asList(token.getRefreshTokenString()));
logger.info("Performing logut from OIDC server with URL: " + logoutUrl);
HttpURLConnection httpURLConnection = performURLEncodedPOSTSendData(logoutUrl, params,
token.getAccessTokenAsBearer());
int responseCode = httpURLConnection.getResponseCode();
if (responseCode == 204) {
logger.info("Logout performed correctly");
return true;
} else {
logger.error("Cannot perfrom logout: [{}] {}", responseCode, httpURLConnection.getResponseMessage());
}
return false;
}
public static byte[] getUserAvatar(URL avatarURL, JWTToken token) {
return getUserAvatar(avatarURL, token != null ? token.getAccessTokenAsBearer() : null);
}
public static byte[] getUserAvatar(URL avatarURL, String authorization) {
logger.debug("Getting user avatar from URL: {}", avatarURL);
ByteArrayOutputStream buffer;
try {
HttpURLConnection conn = (HttpURLConnection) avatarURL.openConnection();
conn.setRequestMethod("GET");
conn.setDoOutput(false);
conn.setDoInput(true);
conn.setRequestProperty("Accept", "image/png, image/gif");
if (authorization != null) {
logger.debug("Adding authorization header as: {}", authorization);
conn.setRequestProperty("Authorization", authorization);
}
if (conn.getResponseCode() == 200) {
String contentType = conn.getContentType();
logger.debug("Getting the stream to the avatar resource with MIME: {}", contentType);
InputStream is = conn.getInputStream();
buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[1024];
while ((nRead = is.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
return buffer.toByteArray();
} else {
logger.debug("Something wrong on the avatar server. Response code: {}", conn.getResponseCode());
}
} catch (FileNotFoundException e) {
logger.debug("User's avatar not found");
} catch (IOException e) {
logger.warn("Downloading user's avatar", e);
}
return null;
}
protected static boolean matchesErrorAndDescription(String jsonString, String expectedError,
String exepectedErrorDescription) {
try {
JSONObject json = (JSONObject) new JSONParser().parse(jsonString);
return expectedError.equals(json.get(RESPONSE_ERROR_KEY))
&& (exepectedErrorDescription == null
|| exepectedErrorDescription.equals(json.get(RESPONSE_ERROR_DESCRIPTION_KEY)));
} catch (ParseException e) {
// Is an unparseable JSON
}
return false;
}
public static boolean isTokenNotActiveError(String jsonString) {
return matchesErrorAndDescription(jsonString, RESPONSE_ERROR_INVALID_GRANT, RESPONSE_ERROR_DESCRIPTION_TINA);
}
public static boolean isInvalidBearerTokenError(String jsonString) {
return matchesErrorAndDescription(jsonString, RESPONSE_ERROR_INVALID_GRANT, RESPONSE_ERROR_DESCRIPTION_IBT);
}
public static boolean isAccessDeniedNotAuthorizedError(String jsonString) {
return matchesErrorAndDescription(jsonString, RESPONSE_ERROR_ACCESS_DENIED,
RESPONSE_ERROR_DESCRIPTION_NOT_AUTHORIZED);
}
}