mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
CTLE-307: added handling for translating 'too big' auth0 access_tokens into a smaller uuid when authorizing
This commit is contained in:
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.exceptions;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Exception thrown doing authentication
|
||||
*
|
||||
*******************************************************************************/
|
||||
public class AccessTokenException extends QAuthenticationException
|
||||
{
|
||||
private Integer statusCode;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor of message
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AccessTokenException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor of message
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AccessTokenException(String message, int statusCode)
|
||||
{
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor of message & cause
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AccessTokenException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor of message & cause
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AccessTokenException(String message, Throwable cause, int statusCode)
|
||||
{
|
||||
super(message, cause);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for statusCode
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer getStatusCode()
|
||||
{
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for statusCode
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setStatusCode(Integer statusCode)
|
||||
{
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for statusCode
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AccessTokenException withStatusCode(Integer statusCode)
|
||||
{
|
||||
this.statusCode = statusCode;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -22,6 +22,7 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.authentication;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher;
|
||||
@ -43,6 +44,31 @@ public class Auth0AuthenticationMetaData extends QAuthenticationMetaData
|
||||
@JsonIgnore
|
||||
private String clientSecret;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// these tables and fields are used to store auth0 application data and access data, the //
|
||||
// access token can potentially be too large to send to qqq because of size limiations, //
|
||||
// so we need to hash it and send the qqq user a version mapped to a smaller token //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
private String clientAuth0ApplicationTableName;
|
||||
private String accessTokenTableName;
|
||||
|
||||
/////////////////////////////////////////
|
||||
// fields on the auth0ApplicationTable //
|
||||
/////////////////////////////////////////
|
||||
private String applicationNameField;
|
||||
private String auth0ClientIdField;
|
||||
private String auth0ClientSecretMaskedField;
|
||||
private Serializable qqqRecordIdField;
|
||||
|
||||
|
||||
/////////////////////////////////////
|
||||
// fields on the accessToken table //
|
||||
/////////////////////////////////////
|
||||
private String clientAuth0ApplicationIdField;
|
||||
private String auth0AccessTokenField;
|
||||
private String qqqAccessTokenField;
|
||||
private String expiresInSecondsField;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -189,4 +215,344 @@ public class Auth0AuthenticationMetaData extends QAuthenticationMetaData
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for clientAuth0ApplicationTableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getClientAuth0ApplicationTableName()
|
||||
{
|
||||
return clientAuth0ApplicationTableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for clientAuth0ApplicationTableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setClientAuth0ApplicationTableName(String clientAuth0ApplicationTableName)
|
||||
{
|
||||
this.clientAuth0ApplicationTableName = clientAuth0ApplicationTableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for clientAuth0ApplicationTableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Auth0AuthenticationMetaData withClientAuth0ApplicationTableName(String clientAuth0ApplicationTableName)
|
||||
{
|
||||
this.clientAuth0ApplicationTableName = clientAuth0ApplicationTableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for accessTokenTableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getAccessTokenTableName()
|
||||
{
|
||||
return accessTokenTableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for accessTokenTableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setAccessTokenTableName(String accessTokenTableName)
|
||||
{
|
||||
this.accessTokenTableName = accessTokenTableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for accessTokenTableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Auth0AuthenticationMetaData withAccessTokenTableName(String accessTokenTableName)
|
||||
{
|
||||
this.accessTokenTableName = accessTokenTableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for applicationNameField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getApplicationNameField()
|
||||
{
|
||||
return applicationNameField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for applicationNameField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setApplicationNameField(String applicationNameField)
|
||||
{
|
||||
this.applicationNameField = applicationNameField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for applicationNameField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Auth0AuthenticationMetaData withApplicationNameField(String applicationNameField)
|
||||
{
|
||||
this.applicationNameField = applicationNameField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for auth0ClientIdField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getAuth0ClientIdField()
|
||||
{
|
||||
return auth0ClientIdField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for auth0ClientIdField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setAuth0ClientIdField(String auth0ClientIdField)
|
||||
{
|
||||
this.auth0ClientIdField = auth0ClientIdField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for auth0ClientIdField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Auth0AuthenticationMetaData withAuth0ClientIdField(String auth0ClientIdField)
|
||||
{
|
||||
this.auth0ClientIdField = auth0ClientIdField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for qqqRecordIdField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Serializable getQqqRecordIdField()
|
||||
{
|
||||
return qqqRecordIdField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for qqqRecordIdField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setQqqRecordIdField(Serializable qqqRecordIdField)
|
||||
{
|
||||
this.qqqRecordIdField = qqqRecordIdField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for qqqRecordIdField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Auth0AuthenticationMetaData withQqqRecordIdField(Serializable qqqRecordIdField)
|
||||
{
|
||||
this.qqqRecordIdField = qqqRecordIdField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for auth0ClientSecretMaskedField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getAuth0ClientSecretMaskedField()
|
||||
{
|
||||
return auth0ClientSecretMaskedField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for auth0ClientSecretMaskedField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setAuth0ClientSecretMaskedField(String auth0ClientSecretMaskedField)
|
||||
{
|
||||
this.auth0ClientSecretMaskedField = auth0ClientSecretMaskedField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for auth0ClientSecretMaskedField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Auth0AuthenticationMetaData withAuth0ClientSecretMaskedField(String auth0ClientSecretMaskedField)
|
||||
{
|
||||
this.auth0ClientSecretMaskedField = auth0ClientSecretMaskedField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for clientAuth0ApplicationIdField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getClientAuth0ApplicationIdField()
|
||||
{
|
||||
return clientAuth0ApplicationIdField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for clientAuth0ApplicationIdField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setClientAuth0ApplicationIdField(String clientAuth0ApplicationIdField)
|
||||
{
|
||||
this.clientAuth0ApplicationIdField = clientAuth0ApplicationIdField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for clientAuth0ApplicationIdField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Auth0AuthenticationMetaData withClientAuth0ApplicationIdField(String clientAuth0ApplicationIdField)
|
||||
{
|
||||
this.clientAuth0ApplicationIdField = clientAuth0ApplicationIdField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for auth0AccessTokenField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getAuth0AccessTokenField()
|
||||
{
|
||||
return auth0AccessTokenField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for auth0AccessTokenField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setAuth0AccessTokenField(String auth0AccessTokenField)
|
||||
{
|
||||
this.auth0AccessTokenField = auth0AccessTokenField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for auth0AccessTokenField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Auth0AuthenticationMetaData withAuth0AccessTokenField(String auth0AccessTokenField)
|
||||
{
|
||||
this.auth0AccessTokenField = auth0AccessTokenField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for qqqAccessTokenField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getQqqAccessTokenField()
|
||||
{
|
||||
return qqqAccessTokenField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for qqqAccessTokenField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setQqqAccessTokenField(String qqqAccessTokenField)
|
||||
{
|
||||
this.qqqAccessTokenField = qqqAccessTokenField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for qqqAccessTokenField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Auth0AuthenticationMetaData withQqqAccessTokenField(String qqqAccessTokenField)
|
||||
{
|
||||
this.qqqAccessTokenField = qqqAccessTokenField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for expiresInSecondsField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getExpiresInSecondsField()
|
||||
{
|
||||
return expiresInSecondsField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for expiresInSecondsField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setExpiresInSecondsField(String expiresInSecondsField)
|
||||
{
|
||||
this.expiresInSecondsField = expiresInSecondsField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for expiresInSecondsField
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Auth0AuthenticationMetaData withExpiresInSecondsField(String expiresInSecondsField)
|
||||
{
|
||||
this.expiresInSecondsField = expiresInSecondsField;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,9 +23,12 @@ package com.kingsrook.qqq.backend.core.modules.authentication;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.AccessTokenException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -54,4 +57,13 @@ public interface QAuthenticationModuleInterface
|
||||
return (false);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
default String createAccessToken(QAuthenticationMetaData metaData, String clientId, String clientSecret) throws AccessTokenException
|
||||
{
|
||||
throw (new NotImplementedException("The method createAccessToken() is not implemented in the class: " + this.getClass().getSimpleName()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import com.auth0.client.auth.AuthAPI;
|
||||
import com.auth0.exception.Auth0Exception;
|
||||
import com.auth0.json.auth.TokenHolder;
|
||||
@ -46,18 +47,42 @@ import com.auth0.jwt.exceptions.JWTDecodeException;
|
||||
import com.auth0.jwt.exceptions.JWTVerificationException;
|
||||
import com.auth0.jwt.exceptions.TokenExpiredException;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import com.auth0.jwt.interfaces.JWTVerifier;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.AccessTokenException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.Auth0AuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QUser;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider;
|
||||
import com.kingsrook.qqq.backend.core.state.SimpleStateKey;
|
||||
import com.kingsrook.qqq.backend.core.state.StateProviderInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
@ -83,7 +108,32 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
public static final String INVALID_TOKEN_ERROR = "An invalid token was provided";
|
||||
|
||||
|
||||
private Instant now;
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// this is how we allow the actions within this class to work without themselves having a logged-in user. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
private static QSession chickenAndEggSession = new QSession()
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for special session
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QSession getChickenAndEggSession()
|
||||
{
|
||||
for(String typeName : QContext.getQInstance().getSecurityKeyTypes().keySet())
|
||||
{
|
||||
QSecurityKeyType keyType = QContext.getQInstance().getSecurityKeyType(typeName);
|
||||
if(StringUtils.hasContent(keyType.getAllAccessKeyName()))
|
||||
{
|
||||
chickenAndEggSession = chickenAndEggSession.withSecurityKeyValue(keyType.getAllAccessKeyName(), true);
|
||||
}
|
||||
}
|
||||
return (chickenAndEggSession);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -93,13 +143,14 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
@Override
|
||||
public QSession createSession(QInstance qInstance, Map<String, String> context) throws QAuthenticationException
|
||||
{
|
||||
Auth0AuthenticationMetaData metaData = (Auth0AuthenticationMetaData) qInstance.getAuthentication();
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// check if we are processing a Basic Auth Session first //
|
||||
///////////////////////////////////////////////////////////
|
||||
if(context.containsKey(BASIC_AUTH_KEY))
|
||||
{
|
||||
Auth0AuthenticationMetaData metaData = (Auth0AuthenticationMetaData) qInstance.getAuthentication();
|
||||
AuthAPI auth = new AuthAPI(metaData.getBaseUrl(), metaData.getClientId(), metaData.getClientSecret());
|
||||
AuthAPI auth = new AuthAPI(metaData.getBaseUrl(), metaData.getClientId(), metaData.getClientSecret());
|
||||
try
|
||||
{
|
||||
/////////////////////////////////////////////////
|
||||
@ -120,10 +171,19 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// get the jwt access token from the context object //
|
||||
//////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// get the jwt id or qqq translated token from the context object //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
String accessToken = context.get(AUTH0_ACCESS_TOKEN_KEY);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// check to see if the session id is a UUID, if so, that means we need to look up the 'actual' token in the access_token table //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(accessToken != null && StringUtils.isUUID(accessToken))
|
||||
{
|
||||
accessToken = lookupActualAccessToken(metaData, accessToken);
|
||||
}
|
||||
|
||||
if(accessToken == null)
|
||||
{
|
||||
LOG.warn(TOKEN_NOT_PROVIDED_ERROR);
|
||||
@ -149,7 +209,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
// if we make it here it means we have never validated this token or its been a long //
|
||||
// enough duration so we need to re-verify the token //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
qSession = revalidateToken(qInstance, accessToken);
|
||||
qSession = revalidateTokenAndBuildSession(qInstance, accessToken);
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// put now into state so we dont check until next interval passes //
|
||||
@ -253,6 +313,14 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
@Override
|
||||
public boolean isSessionValid(QInstance instance, QSession session)
|
||||
{
|
||||
if(session == chickenAndEggSession)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// this is how we allow the actions within this class to work without themselves having a logged-in user. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
return (true);
|
||||
}
|
||||
|
||||
if(session == null)
|
||||
{
|
||||
return (false);
|
||||
@ -283,7 +351,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
try
|
||||
{
|
||||
LOG.debug("Re-validating token due to validation interval being passed: " + session.getIdReference());
|
||||
revalidateToken(instance, session.getIdReference());
|
||||
revalidateTokenAndBuildSession(instance, session.getIdReference());
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// update the timestamp in state provider, to avoid re-checking //
|
||||
@ -308,28 +376,37 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
** makes request to check if a token is still valid and build new qSession if it is
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QSession revalidateToken(QInstance qInstance, String accessToken) throws JwkException
|
||||
private QSession revalidateTokenAndBuildSession(QInstance qInstance, String accessToken) throws JwkException
|
||||
{
|
||||
Auth0AuthenticationMetaData metaData = (Auth0AuthenticationMetaData) qInstance.getAuthentication();
|
||||
|
||||
DecodedJWT jwt = JWT.decode(accessToken);
|
||||
JwkProvider provider = new UrlJwkProvider(metaData.getBaseUrl());
|
||||
Jwk jwk = provider.get(jwt.getKeyId());
|
||||
Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null);
|
||||
JWTVerifier verifier = JWT.require(algorithm)
|
||||
.withIssuer(metaData.getBaseUrl())
|
||||
.build();
|
||||
|
||||
///////////////////////////////////
|
||||
// make call to verify the token //
|
||||
///////////////////////////////////
|
||||
verifier.verify(accessToken);
|
||||
|
||||
validateToken(qInstance, accessToken);
|
||||
return (buildQSessionFromToken(accessToken, qInstance));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** tests validity of a token
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateToken(QInstance qInstance, String tokenString) throws JwkException
|
||||
{
|
||||
Auth0AuthenticationMetaData metaData = (Auth0AuthenticationMetaData) qInstance.getAuthentication();
|
||||
|
||||
DecodedJWT idToken = JWT.decode(tokenString);
|
||||
JwkProvider provider = new UrlJwkProvider(metaData.getBaseUrl());
|
||||
Jwk jwk = provider.get(idToken.getKeyId());
|
||||
Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null);
|
||||
JWT.require(algorithm)
|
||||
.withIssuer(idToken.getIssuer())
|
||||
.build()
|
||||
.verify(idToken);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** extracts info from token creating a QSession
|
||||
**
|
||||
@ -499,10 +576,13 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
}
|
||||
else
|
||||
{
|
||||
String value = securityKeyValues.optString(jsonKey);
|
||||
if(value != null)
|
||||
String values = securityKeyValues.optString(jsonKey);
|
||||
if(StringUtils.hasContent(values))
|
||||
{
|
||||
qSession.withSecurityKeyValue(securityKeyName, value);
|
||||
for(String v : values.split(","))
|
||||
{
|
||||
qSession.withSecurityKeyValue(securityKeyName, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -519,4 +599,185 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
return (InMemoryStateProvider.getInstance());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Load an instance of the appropriate state provider
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String createAccessToken(QAuthenticationMetaData metaData, String clientId, String clientSecret) throws AccessTokenException
|
||||
{
|
||||
QSession sessionBefore = QContext.getQSession();
|
||||
Auth0AuthenticationMetaData auth0MetaData = (Auth0AuthenticationMetaData) metaData;
|
||||
|
||||
try
|
||||
{
|
||||
QContext.setQSession(getChickenAndEggSession());
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// fetch the application from database, will throw accesstokenexception if not found //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
QRecord clientAuth0Application = getClientAuth0Application(auth0MetaData, clientId);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// request access token from auth0 if exception is not thrown, that means 200OK, we want to //
|
||||
// store the actual access token in the database, and return a unique value //
|
||||
// back to the user which will be what they use on subseqeunt requests (because token too big) //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
JSONObject accessTokenData = requestAccessTokenFromAuth0(auth0MetaData, clientId, clientSecret);
|
||||
|
||||
Integer expiresInSeconds = accessTokenData.getInt("expires_in");
|
||||
String accessToken = accessTokenData.getString("access_token");
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
|
||||
/////////////////////////////////
|
||||
// store the details in the db //
|
||||
/////////////////////////////////
|
||||
QRecord accessTokenRecord = new QRecord()
|
||||
.withValue(auth0MetaData.getClientAuth0ApplicationIdField(), clientAuth0Application.getValue("id"))
|
||||
.withValue(auth0MetaData.getAuth0AccessTokenField(), accessToken)
|
||||
.withValue(auth0MetaData.getQqqAccessTokenField(), uuid)
|
||||
.withValue(auth0MetaData.getExpiresInSecondsField(), expiresInSeconds);
|
||||
InsertInput input = new InsertInput();
|
||||
input.setTableName(auth0MetaData.getAccessTokenTableName());
|
||||
input.setRecords(List.of(accessTokenRecord));
|
||||
new InsertAction().execute(input);
|
||||
|
||||
//////////////////////////////////
|
||||
// update and send the response //
|
||||
//////////////////////////////////
|
||||
accessTokenData.put("access_token", uuid);
|
||||
accessTokenData.remove("scope");
|
||||
return (accessTokenData.toString());
|
||||
}
|
||||
catch(AccessTokenException ate)
|
||||
{
|
||||
throw (ate);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new AccessTokenException(e.getMessage(), e));
|
||||
}
|
||||
finally
|
||||
{
|
||||
QContext.setQSession(sessionBefore);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** make http request to Auth0 for a new access token
|
||||
**
|
||||
*******************************************************************************/
|
||||
public JSONObject requestAccessTokenFromAuth0(Auth0AuthenticationMetaData auth0MetaData, String clientId, String clientSecret) throws AccessTokenException
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// make a request to Auth0 using the client_id and client_secret //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
try(CloseableHttpClient httpClient = HttpClientBuilder.create().build())
|
||||
{
|
||||
UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(List.of(
|
||||
new BasicNameValuePair("content-type", "application/x-www-form-urlencoded"),
|
||||
new BasicNameValuePair("grant_type", "client_credentials"),
|
||||
new BasicNameValuePair("audience", auth0MetaData.getAudience()),
|
||||
new BasicNameValuePair("client_id", clientId),
|
||||
new BasicNameValuePair("client_secret", clientSecret)));
|
||||
|
||||
HttpPost request = new HttpPost(auth0MetaData.getBaseUrl() + "oauth/token");
|
||||
request.setEntity(urlEncodedFormEntity);
|
||||
|
||||
try(CloseableHttpResponse response = httpClient.execute(request))
|
||||
{
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
String content = EntityUtils.toString(response.getEntity());
|
||||
|
||||
//////////////////////////////////////
|
||||
// if 200OK, return the json object //
|
||||
//////////////////////////////////////
|
||||
if(statusCode == 200)
|
||||
{
|
||||
return (JsonUtils.toJSONObject(content));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if not 200, throw an access token exception with the message and status code of the non-200 response //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
throw (new AccessTokenException(content, statusCode));
|
||||
}
|
||||
}
|
||||
catch(AccessTokenException ate)
|
||||
{
|
||||
throw (ate);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new AccessTokenException(e.getMessage(), e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Look up access_token record, return if found.
|
||||
**
|
||||
*******************************************************************************/
|
||||
String lookupActualAccessToken(Auth0AuthenticationMetaData metaData, String qqqAccessToken)
|
||||
{
|
||||
String accessToken = null;
|
||||
QSession beforeSession = QContext.getQSession();
|
||||
|
||||
try
|
||||
{
|
||||
QContext.setQSession(getChickenAndEggSession());
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// try to look up existing auth0 application from database, insert one if not found //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(metaData.getAccessTokenTableName());
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(metaData.getQqqAccessTokenField(), QCriteriaOperator.EQUALS, qqqAccessToken)));
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(queryOutput.getRecords()))
|
||||
{
|
||||
accessToken = queryOutput.getRecords().get(0).getValueString(metaData.getAuth0AccessTokenField());
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Could not find Auth0 access token for provided qqq access token", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
QContext.setQSession(beforeSession);
|
||||
}
|
||||
|
||||
return (accessToken);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Look up client_auth0_application record, return if found.
|
||||
**
|
||||
*******************************************************************************/
|
||||
QRecord getClientAuth0Application(Auth0AuthenticationMetaData metaData, String clientId) throws QException
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// try to look up existing auth0 application from database, insert one if not found //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(metaData.getClientAuth0ApplicationTableName());
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(metaData.getAuth0ClientIdField(), QCriteriaOperator.EQUALS, clientId)));
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(queryOutput.getRecords()))
|
||||
{
|
||||
return (queryOutput.getRecords().get(0));
|
||||
}
|
||||
|
||||
throw (new AccessTokenException("This client has not been configured to use the API.", HttpStatus.SC_UNAUTHORIZED));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,7 +24,9 @@ package com.kingsrook.qqq.backend.core.modules.authentication.implementations;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.AccessTokenException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QUser;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
|
||||
@ -36,6 +38,9 @@ import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModu
|
||||
*******************************************************************************/
|
||||
public class FullyAnonymousAuthenticationModule implements QAuthenticationModuleInterface
|
||||
{
|
||||
public static final String TEST_ACCESS_TOKEN = "b0a88d00-8439-48e8-8b48-e0ef40c40ed9";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
@ -71,4 +76,16 @@ public class FullyAnonymousAuthenticationModule implements QAuthenticationModule
|
||||
{
|
||||
return session != null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Load an instance of the appropriate state provider
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String createAccessToken(QAuthenticationMetaData metaData, String clientId, String clientSecret) throws AccessTokenException
|
||||
{
|
||||
return (TEST_ACCESS_TOKEN);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -437,4 +437,14 @@ public class StringUtils
|
||||
return (s.substring(0, 1).toUpperCase() + s.substring(1));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** determines if a given string is a UUID
|
||||
*******************************************************************************/
|
||||
public static boolean isUUID(String s)
|
||||
{
|
||||
return (Pattern.matches("[a-f0-9]{8}(?:-[a-f0-9]{4}){4}[a-f0-9]{8}", s));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -64,7 +64,6 @@ import static org.mockito.Mockito.verify;
|
||||
*******************************************************************************/
|
||||
public class Auth0AuthenticationModuleTest extends BaseTest
|
||||
{
|
||||
private static final String VALID_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IllrY2FkWTA0Q3RFVUFxQUdLNTk3ayJ9.eyJnaXZlbl9uYW1lIjoiVGltIiwiZmFtaWx5X25hbWUiOiJDaGFtYmVybGFpbiIsIm5pY2tuYW1lIjoidGltLmNoYW1iZXJsYWluIiwibmFtZSI6IlRpbSBDaGFtYmVybGFpbiIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS0vQUZkWnVjcXVSaUFvTzk1RG9URklnbUtseVA1akVBVnZmWXFnS0lHTkVubzE9czk2LWMiLCJsb2NhbGUiOiJlbiIsInVwZGF0ZWRfYXQiOiIyMDIyLTA3LTE5VDE2OjI0OjQ1LjgyMloiLCJlbWFpbCI6InRpbS5jaGFtYmVybGFpbkBraW5nc3Jvb2suY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzcyI6Imh0dHBzOi8va2luZ3Nyb29rLnVzLmF1dGgwLmNvbS8iLCJzdWIiOiJnb29nbGUtb2F1dGgyfDEwODk2NDEyNjE3MjY1NzAzNDg2NyIsImF1ZCI6InNwQ1NtczAzcHpVZGRYN1BocHN4ZDlUd2FLMDlZZmNxIiwiaWF0IjoxNjU4MjQ3OTAyLCJleHAiOjE2NTgyODM5MDIsIm5vbmNlIjoiZUhOdFMxbEtUR2N5ZG5KS1VVY3RkRTFVT0ZKNmJFNUxVVkEwZEdsRGVXOXZkVkl4UW41eVRrUlJlZz09In0.hib7JR8NDU2kx8Fj1bnzo3IUuabE6Hb-Z7HHZAJPQuF_Zdg3L1KDypn6SY7HAd_dsz2N8RkXfvQto-Y2g2ukuz7FxzNFgcVL99cyEO3YqmyCa6JTOTCrxdeaIE8QZpCEKvC28oeJBv0wO1Dwc--OVJMsK2vSzyxj1WNok64YYjWKLL4c0dFf-nj0KWFr1IU-tMiyWLDDiJw2Sa8M4YxXZYqdlkgNmrBPExgcm9l9SiT2l3Ts3Sgc_IyMVyMrnV8XX50EWdsm6vuCOSUcqf0XhjDQ7urZveoVwVLnYq3GcLhVBcy1Hr9RL8zPdPynOzsbX6uCww2Esrv6iwWrgQ5zBA";
|
||||
private static final String INVALID_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IllrY2FkWTA0Q3RFVUFxQUdLNTk3ayJ9.eyJnaXZlbl9uYW1lIjoiVGltIiwiZmFtaWx5X25hbWUiOiJDaGFtYmVybGFpbiIsIm5pY2tuYW1lIjoidGltLmNoYW1iZXJsYWluIiwibmFtZSI6IlRpbSBDaGFtYmVybGFpbiIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS0vQUZkWnVjcXVSaUFvTzk1RG9URklnbUtseVA1akVBVnZmWXFnS0lHTkVubzE9czk2LWMiLCJsb2NhbGUiOiJlbiIsInVwZGF0ZWRfYXQiOiIyMDIyLTA3LTE5VDE2OjI0OjQ1LjgyMloiLCJlbWFpbCI6InRpbS5jaGFtYmVybGFpbkBraW5nc3Jvb2suY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzcyI6Imh0dHBzOi8va2luZ3Nyb29rLnVzLmF1dGgwLmNvbS8iLCJzdWIiOiJnb29nbGUtb2F1dGgyfDEwODk2NDEyNjE3MjY1NzAzNDg2NyIsImF1ZCI6InNwQ1NtczAzcHpVZGRYN1BocHN4ZDlUd2FLMDlZZmNxIiwiaWF0IjoxNjU4MjQ3OTAyLCJleHAiOjE2NTgyODM5MDIsIm5vbmNlIjoiZUhOdFMxbEtUR2N5ZG5KS1VVY3RkRTFVT0ZKNmJFNUxVVkEwZEdsRGVXOXZkVkl4UW41eVRrUlJlZz09In0.hib7JR8NDU2kx8Fj1bnzo3IUuabE6Hb-Z7HHZAJPQuF_Zdg3L1KDypn6SY7HAd_dsz2N8RkXfvQto-Y2g2ukuz7FxzNFgcVL99cyEO3YqmyCa6JTOTCrxdeaIE8QZpCEKvC28oeJBv0wO1Dwc--OVJMsK2vSzyxj1WNok64YYjWKLL4c0dFf-nj0KWFr1IU-tMiyWLDDiJw2Sa8M4YxXZYqdlkgNmrBPExgcm9l9SiT2l3Ts3Sgc_IyMVyMrnV8XX50EWdsm6vuCOSUcqf0XhjDQ7urZveoVwVLnYq3GcLhVBcy1Hr9RL8zPdPynOzsbX6uCww2Esrv6iwWrgQ5zBA-thismakesinvalid";
|
||||
private static final String EXPIRED_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IllrY2FkWTA0Q3RFVUFxQUdLNTk3ayJ9.eyJnaXZlbl9uYW1lIjoiVGltIiwiZmFtaWx5X25hbWUiOiJDaGFtYmVybGFpbiIsIm5pY2tuYW1lIjoidGltLmNoYW1iZXJsYWluIiwibmFtZSI6IlRpbSBDaGFtYmVybGFpbiIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS0vQUZkWnVjcXVSaUFvTzk1RG9URklnbUtseVA1akVBVnZmWXFnS0lHTkVubzE9czk2LWMiLCJsb2NhbGUiOiJlbiIsInVwZGF0ZWRfYXQiOiIyMDIyLTA3LTE4VDIxOjM4OjE1LjM4NloiLCJlbWFpbCI6InRpbS5jaGFtYmVybGFpbkBraW5nc3Jvb2suY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImlzcyI6Imh0dHBzOi8va2luZ3Nyb29rLnVzLmF1dGgwLmNvbS8iLCJzdWIiOiJnb29nbGUtb2F1dGgyfDEwODk2NDEyNjE3MjY1NzAzNDg2NyIsImF1ZCI6InNwQ1NtczAzcHpVZGRYN1BocHN4ZDlUd2FLMDlZZmNxIiwiaWF0IjoxNjU4MTgwNDc3LCJleHAiOjE2NTgyMTY0NzcsIm5vbmNlIjoiVkZkQlYzWmplR2hvY1cwMk9WZEtabHBLU0c1K1ZXbElhMEV3VkZaeFpVdEJVMDErZUZaT1RtMTNiZz09In0.fU7EwUgNrupOPz_PX_aQKON2xG1-LWD85xVo1Bn41WNEek-iMyJoch8l6NUihi7Bou14BoOfeWIG_sMqsLHqI2Pk7el7l1kigsjURx0wpiXadBt8piMxdIlxdToZEMuZCBzg7eJvXh4sM8tlV5cm0gPa6FT9Ih3VGJajNlXi5BcYS_JRpIvFvHn8-Bxj4KiAlZ5XPPkopjnDgP8kFfc4cMn_nxDkqWYlhj-5TaGW2xCLC9Qr_9UNxX0fm-CkKjYs3Z5ezbiXNkc-bxrCYvxeBeDPf8-T3EqrxCRVqCZSJ85BHdOc_E7UZC_g8bNj0umoplGwlCbzO4XIuOO-KlIaOg";
|
||||
private static final String UNDECODABLE_TOKEN = "UNDECODABLE";
|
||||
|
@ -48,6 +48,7 @@ import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.AccessTokenException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
|
||||
@ -74,9 +75,12 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.Auth0AuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
@ -129,6 +133,11 @@ public class QJavalinApiHandler
|
||||
{
|
||||
return (() ->
|
||||
{
|
||||
/////////////////////////////
|
||||
// authentication endpoint //
|
||||
/////////////////////////////
|
||||
ApiBuilder.post("/api/oauth/token", QJavalinApiHandler::handleAuthorization);
|
||||
|
||||
ApiBuilder.path("/api/{version}", () -> // todo - configurable, that /api/ bit?
|
||||
{
|
||||
ApiBuilder.get("/openapi.yaml", QJavalinApiHandler::doSpecYaml);
|
||||
@ -243,6 +252,78 @@ public class QJavalinApiHandler
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void handleAuthorization(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
//////////////////////////////
|
||||
// validate required inputs //
|
||||
//////////////////////////////
|
||||
String clientId = context.formParam("client_id");
|
||||
if(clientId == null)
|
||||
{
|
||||
context.status(HttpStatus.BAD_REQUEST_400);
|
||||
context.result("'client_id' must be provided.");
|
||||
return;
|
||||
}
|
||||
String clientSecret = context.formParam("client_secret");
|
||||
if(clientSecret == null)
|
||||
{
|
||||
context.status(HttpStatus.BAD_REQUEST_400);
|
||||
context.result("'client_secret' must be provided.");
|
||||
return;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// get the auth0 authentication module from qInstance //
|
||||
////////////////////////////////////////////////////////
|
||||
Auth0AuthenticationMetaData metaData = (Auth0AuthenticationMetaData) qInstance.getAuthentication();
|
||||
QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher();
|
||||
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(qInstance.getAuthentication());
|
||||
|
||||
try
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// make call to get access token data, if no exception thrown, assume 200OK and return //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
QContext.init(qInstance, null); // hmm...
|
||||
String accessToken = authenticationModule.createAccessToken(metaData, clientId, clientSecret);
|
||||
context.status(io.javalin.http.HttpStatus.OK);
|
||||
context.result(accessToken);
|
||||
QJavalinAccessLogger.logEndSuccess();
|
||||
return;
|
||||
}
|
||||
catch(AccessTokenException aae)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// if the exception has a status code, then return that code and message //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
if(aae.getStatusCode() != null)
|
||||
{
|
||||
context.status(aae.getStatusCode());
|
||||
context.result(aae.getMessage());
|
||||
QJavalinAccessLogger.logEndSuccess();
|
||||
return;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// if no code, throw and handle like other exceptions //
|
||||
////////////////////////////////////////////////////////
|
||||
throw (aae);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
handleException(context, e);
|
||||
QJavalinAccessLogger.logEndFail(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -46,6 +46,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.implementations.FullyAnonymousAuthenticationModule;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
|
||||
import kong.unirest.HttpResponse;
|
||||
@ -75,6 +76,9 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
|
||||
protected static QJavalinImplementation qJavalinImplementation;
|
||||
|
||||
private static final String OAUTH_CLIENT_ID = "test-oauth-client-id";
|
||||
private static final String OAUTH_CLIENT_SECRET = "test-oauth-client-secret";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -998,6 +1002,56 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testAuthorizeNoParams()
|
||||
{
|
||||
///////////////
|
||||
// no params //
|
||||
///////////////
|
||||
HttpResponse<String> response = Unirest.post(BASE_URL + "/api/oauth/token").asString();
|
||||
assertEquals(HttpStatus.BAD_REQUEST_400, response.getStatus());
|
||||
assertThat(response.getBody()).contains("client_id");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testAuthorizeOneParam()
|
||||
{
|
||||
///////////////
|
||||
// no params //
|
||||
///////////////
|
||||
HttpResponse<String> response = Unirest.post(BASE_URL + "/api/oauth/token")
|
||||
.body("client_id=XXXXXXXXXX").asString();
|
||||
assertEquals(HttpStatus.BAD_REQUEST_400, response.getStatus());
|
||||
assertThat(response.getBody()).contains("client_secret");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testAuthorizeAllParams()
|
||||
{
|
||||
///////////////
|
||||
// no params //
|
||||
///////////////
|
||||
HttpResponse<String> response = Unirest.post(BASE_URL + "/api/oauth/token")
|
||||
.body("client_id=XXXXXXXXXX&client_secret=YYYYYYYYYYYY").asString();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
assertThat(response.getBody()).isEqualTo(FullyAnonymousAuthenticationModule.TEST_ACCESS_TOKEN);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -1124,4 +1178,4 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user