mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
Add api logs user, security fields
This commit is contained in:
@ -42,6 +42,7 @@ import com.kingsrook.qqq.api.model.APILog;
|
||||
import com.kingsrook.qqq.api.model.APIVersion;
|
||||
import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecInput;
|
||||
import com.kingsrook.qqq.api.model.actions.GenerateOpenApiSpecOutput;
|
||||
import com.kingsrook.qqq.api.model.metadata.APILogMetaDataProvider;
|
||||
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
|
||||
import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
|
||||
@ -85,6 +86,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.branding.QBrandingMetaData;
|
||||
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.model.session.QUser;
|
||||
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;
|
||||
@ -121,6 +123,8 @@ public class QJavalinApiHandler
|
||||
|
||||
private static Map<String, Map<String, QTableMetaData>> tableApiNameMap = new HashMap<>();
|
||||
|
||||
private static Map<String, Integer> apiLogUserIdCache = new HashMap<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -366,6 +370,8 @@ public class QJavalinApiHandler
|
||||
}
|
||||
catch(AccessTokenException aae)
|
||||
{
|
||||
LOG.info("Error getting api access token", aae, logPair("clientId", clientId));
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// if the exception has a status code, then return that code and message //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@ -652,10 +658,27 @@ public class QJavalinApiHandler
|
||||
{
|
||||
if(QContext.getQInstance().getTable(APILog.TABLE_NAME) != null)
|
||||
{
|
||||
QSession qSession = QContext.getQSession();
|
||||
if(qSession != null)
|
||||
{
|
||||
for(Map.Entry<String, List<Serializable>> entry : CollectionUtils.nonNullMap(qSession.getSecurityKeyValues()).entrySet())
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// put the 1st entry for this key in the api log record //
|
||||
// todo - might need revisited for users with multiple values... e.g., look for the security key in records in the request? or as part of the URL //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(CollectionUtils.nullSafeHasContents(entry.getValue()))
|
||||
{
|
||||
apiLog.withSecurityKeyValue(entry.getKey(), entry.getValue().get(0));
|
||||
}
|
||||
}
|
||||
|
||||
Integer userId = getApiLogUserId(qSession);
|
||||
apiLog.setApiLogUserId(userId);
|
||||
}
|
||||
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(APILog.TABLE_NAME);
|
||||
// todo - security fields!!!!!
|
||||
// todo - user!!!!
|
||||
insertInput.setRecords(List.of(apiLog.toQRecord()));
|
||||
new InsertAction().executeAsync(insertInput);
|
||||
}
|
||||
@ -668,6 +691,129 @@ public class QJavalinApiHandler
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static Integer getApiLogUserId(QSession qSession) throws QException
|
||||
{
|
||||
String tableName = APILogMetaDataProvider.TABLE_NAME_API_LOG_USER;
|
||||
|
||||
if(qSession == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
QUser qUser = qSession.getUser();
|
||||
if(qUser == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
String userName = qUser.getFullName();
|
||||
if(!StringUtils.hasContent(userName))
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// if we haven't cached this username to an id, query and/or insert it now //
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
if(!apiLogUserIdCache.containsKey(userName))
|
||||
{
|
||||
//////////////////////////////////////////////////////////////
|
||||
// first try to get - if it's found, cache it and return it //
|
||||
//////////////////////////////////////////////////////////////
|
||||
Integer id = fetchApiLogUserIdFromName(userName);
|
||||
if(id != null)
|
||||
{
|
||||
apiLogUserIdCache.put(userName, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
///////////////////////////////////////////////////////
|
||||
// if it wasn't found from a Get, then try an Insert //
|
||||
///////////////////////////////////////////////////////
|
||||
LOG.debug("Inserting " + tableName + " named " + userName);
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(tableName);
|
||||
QRecord record = new QRecord().withValue("name", userName);
|
||||
|
||||
for(Map.Entry<String, List<Serializable>> entry : CollectionUtils.nonNullMap(qSession.getSecurityKeyValues()).entrySet())
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// put the 1st entry for this key in the api log user record //
|
||||
// todo - might need revisited for users with multiple values... e.g., look for the security key in records in the request? or as part of the URL //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(CollectionUtils.nullSafeHasContents(entry.getValue()))
|
||||
{
|
||||
record.withValue(entry.getKey(), entry.getValue().get(0));
|
||||
}
|
||||
}
|
||||
|
||||
insertInput.setRecords(List.of(record));
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
id = insertOutput.getRecords().get(0).getValueInteger("id");
|
||||
|
||||
////////////////////////////////////////
|
||||
// if we got an id, cache & return it //
|
||||
////////////////////////////////////////
|
||||
if(id != null)
|
||||
{
|
||||
apiLogUserIdCache.put(userName, id);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// assume this may mean a dupe-key - so - try another fetch below //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
LOG.debug("Caught error inserting " + tableName + " named " + userName + " - will try to re-fetch", e);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// if the insert failed, try another fetch (e.g., after a UK violation) //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
id = fetchApiLogUserIdFromName(userName);
|
||||
if(id != null)
|
||||
{
|
||||
apiLogUserIdCache.put(userName, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/////////////
|
||||
// give up //
|
||||
/////////////
|
||||
LOG.error("Unable to get id for " + tableName + " named " + userName);
|
||||
return (null);
|
||||
}
|
||||
|
||||
return (apiLogUserIdCache.get(userName));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static Integer fetchApiLogUserIdFromName(String name) throws QException
|
||||
{
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(APILogMetaDataProvider.TABLE_NAME_API_LOG_USER);
|
||||
getInput.setUniqueKey(Map.of("name", name));
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
if(getOutput.getRecord() != null)
|
||||
{
|
||||
return (getOutput.getRecord().getValueInteger("id"));
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -22,15 +22,22 @@
|
||||
package com.kingsrook.qqq.api.model;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.api.model.metadata.APILogMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** In addition to the standard/known fields in this entity, you can also add
|
||||
** name/value pairs of security key values - e.g., a clientId field
|
||||
*******************************************************************************/
|
||||
public class APILog extends QRecordEntity
|
||||
{
|
||||
@ -42,6 +49,9 @@ public class APILog extends QRecordEntity
|
||||
@QField(isEditable = false)
|
||||
private Instant timestamp;
|
||||
|
||||
@QField(possibleValueSourceName = APILogMetaDataProvider.TABLE_NAME_API_LOG_USER, label = "User")
|
||||
private Integer apiLogUserId;
|
||||
|
||||
@QField()
|
||||
private String method;
|
||||
|
||||
@ -63,6 +73,8 @@ public class APILog extends QRecordEntity
|
||||
@QField()
|
||||
private String responseBody;
|
||||
|
||||
private Map<String, Serializable> securityKeyValues = new HashMap<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -75,6 +87,24 @@ public class APILog extends QRecordEntity
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QRecord toQRecord() throws QRuntimeException
|
||||
{
|
||||
QRecord qRecord = super.toQRecord();
|
||||
|
||||
for(Map.Entry<String, Serializable> entry : CollectionUtils.nonNullMap(this.securityKeyValues).entrySet())
|
||||
{
|
||||
qRecord.setValue(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
return (qRecord);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
@ -390,4 +420,81 @@ public class APILog extends QRecordEntity
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for securityKeyValues
|
||||
*******************************************************************************/
|
||||
public Map<String, Serializable> getSecurityKeyValues()
|
||||
{
|
||||
return (this.securityKeyValues);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for securityKeyValues
|
||||
*******************************************************************************/
|
||||
public void setSecurityKeyValues(Map<String, Serializable> securityKeyValues)
|
||||
{
|
||||
this.securityKeyValues = securityKeyValues;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for securityKeyValues
|
||||
*******************************************************************************/
|
||||
public APILog withSecurityKeyValues(Map<String, Serializable> securityKeyValues)
|
||||
{
|
||||
this.securityKeyValues = securityKeyValues;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for securityKeyValues
|
||||
*******************************************************************************/
|
||||
public APILog withSecurityKeyValue(String key, Serializable value)
|
||||
{
|
||||
if(this.securityKeyValues == null)
|
||||
{
|
||||
this.securityKeyValues = new HashMap<>();
|
||||
}
|
||||
this.securityKeyValues.put(key, value);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for apiLogUserId
|
||||
*******************************************************************************/
|
||||
public Integer getApiLogUserId()
|
||||
{
|
||||
return (this.apiLogUserId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for apiLogUserId
|
||||
*******************************************************************************/
|
||||
public void setApiLogUserId(Integer apiLogUserId)
|
||||
{
|
||||
this.apiLogUserId = apiLogUserId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for apiLogUserId
|
||||
*******************************************************************************/
|
||||
public APILog withApiLogUserId(Integer apiLogUserId)
|
||||
{
|
||||
this.apiLogUserId = apiLogUserId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,12 +29,16 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
|
||||
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.fields.ValueTooLongBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -42,13 +46,63 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
*******************************************************************************/
|
||||
public class APILogMetaDataProvider
|
||||
{
|
||||
public static final String TABLE_NAME_API_LOG = "apiLog";
|
||||
public static final String TABLE_NAME_API_LOG_USER = "apiLogUser";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void defineAll(QInstance qInstance, String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
|
||||
{
|
||||
defineApiLogUserPvs(qInstance);
|
||||
defineAPILogTable(qInstance, backendName, backendDetailEnricher);
|
||||
defineAPILogUserTable(qInstance, backendName, backendDetailEnricher);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void defineApiLogUserPvs(QInstance instance)
|
||||
{
|
||||
instance.addPossibleValueSource(new QPossibleValueSource()
|
||||
.withName(TABLE_NAME_API_LOG_USER)
|
||||
.withTableName(TABLE_NAME_API_LOG_USER));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void defineAPILogUserTable(QInstance qInstance, String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
|
||||
{
|
||||
QTableMetaData tableMetaData = new QTableMetaData()
|
||||
.withName(TABLE_NAME_API_LOG_USER)
|
||||
.withLabel("API Log User")
|
||||
.withIcon(new QIcon().withName("person"))
|
||||
.withBackendName(backendName)
|
||||
.withRecordLabelFormat("%s")
|
||||
.withRecordLabelFields("name")
|
||||
.withPrimaryKeyField("id")
|
||||
.withUniqueKey(new UniqueKey("name"))
|
||||
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
|
||||
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
|
||||
.withField(new QFieldMetaData("name", QFieldType.STRING).withIsRequired(true))
|
||||
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "name")))
|
||||
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")))
|
||||
.withoutCapabilities(Capability.TABLE_INSERT, Capability.TABLE_UPDATE, Capability.TABLE_DELETE);
|
||||
|
||||
if(backendDetailEnricher != null)
|
||||
{
|
||||
backendDetailEnricher.accept(tableMetaData);
|
||||
}
|
||||
|
||||
qInstance.addTable(tableMetaData);
|
||||
}
|
||||
|
||||
|
||||
@ -59,14 +113,14 @@ public class APILogMetaDataProvider
|
||||
private static void defineAPILogTable(QInstance qInstance, String backendName, Consumer<QTableMetaData> backendDetailEnricher) throws QException
|
||||
{
|
||||
QTableMetaData tableMetaData = new QTableMetaData()
|
||||
.withName("apiLog")
|
||||
.withName(TABLE_NAME_API_LOG)
|
||||
.withLabel("API Log")
|
||||
.withIcon(new QIcon().withName("data_object"))
|
||||
.withBackendName(backendName)
|
||||
.withRecordLabelFormat("%s")
|
||||
.withPrimaryKeyField("id")
|
||||
.withFieldsFromEntity(APILog.class)
|
||||
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id")))
|
||||
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "apiLogUserId")))
|
||||
.withSection(new QFieldSection("request", new QIcon().withName("arrow_upward"), Tier.T2, List.of("method", "version", "path", "queryString", "requestBody")))
|
||||
.withSection(new QFieldSection("response", new QIcon().withName("arrow_downward"), Tier.T2, List.of("statusCode", "responseBody")))
|
||||
.withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("timestamp")))
|
||||
|
Reference in New Issue
Block a user