|
|
|
@@ -35,14 +35,19 @@ import com.kingsrook.qbits.collaboration.PersonalRecords;
|
|
|
|
|
import com.kingsrook.qbits.collaboration.PersonalServiceFixture;
|
|
|
|
|
import com.kingsrook.qbits.collaboration.favorites.FavoriteEntry;
|
|
|
|
|
import com.kingsrook.qbits.collaboration.model.CollaborationTables;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.actions.permissions.CustomPermissionChecker;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
|
|
|
|
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.QPermissionDeniedException;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
|
|
|
|
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.QQueryFilter;
|
|
|
|
@@ -51,11 +56,17 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
|
|
|
|
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.permissions.MetaDataWithPermissionRules;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.model.metadata.permissions.PermissionLevel;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.simple.SimpleSchedulerMetaData;
|
|
|
|
|
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.QSystemUserSession;
|
|
|
|
|
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
|
|
|
|
@@ -158,6 +169,70 @@ class RecentsServiceTest extends PersonalServiceFixture
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*******************************************************************************
|
|
|
|
|
** A table whose permission checker goes by the action input it is handed (a
|
|
|
|
|
** query, a get or a count reads; anything else is refused as a write) is a
|
|
|
|
|
** target for whoever that checker lets read it, and for nobody else: the
|
|
|
|
|
** visibility check asks as the query it stands for, never with an untyped
|
|
|
|
|
** input the checker cannot recognise.
|
|
|
|
|
*******************************************************************************/
|
|
|
|
|
@Test
|
|
|
|
|
void tableWithAnInputTypedPermissionCheckerIsATarget() throws Exception
|
|
|
|
|
{
|
|
|
|
|
instance.addTable(new QTableMetaData().withName("ledger").withLabel("Ledger").withBackendName("memory").withPrimaryKeyField("id")
|
|
|
|
|
.withPermissionRules(new QPermissionRules().withLevel(PermissionLevel.HAS_ACCESS_PERMISSION)
|
|
|
|
|
.withCustomPermissionChecker(new QCodeReference(ReadsByInputType.class)))
|
|
|
|
|
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
|
|
|
|
.withField(new QFieldMetaData("title", QFieldType.STRING))
|
|
|
|
|
.withRecordLabelFormatAndFields("%s", "title"));
|
|
|
|
|
InsertInput seed = new InsertInput("ledger").withRecords(List.of(new QRecord().withValue("id", 7).withValue("title", "Seventh entry")));
|
|
|
|
|
seed.withPermissionBypass("Test fixture seeds a row of a table nobody may write.");
|
|
|
|
|
assertThat(new InsertAction().execute(seed).getRecords()).allSatisfy(row -> assertThat(row.getErrors()).isNullOrEmpty());
|
|
|
|
|
|
|
|
|
|
login("alice", ReadsByInputType.PERMISSION);
|
|
|
|
|
at(START);
|
|
|
|
|
RecentsService.recordView("TABLE", "ledger", null);
|
|
|
|
|
at(START.plusSeconds(1));
|
|
|
|
|
RecentsService.recordView("RECORD", "ledger", "7");
|
|
|
|
|
assertThat(RecentsService.list(50)).extracting(FavoriteEntry::label).containsExactly("Seventh entry", "Ledger");
|
|
|
|
|
|
|
|
|
|
login("bob");
|
|
|
|
|
assertThatThrownBy(() -> RecentsService.recordView("TABLE", "ledger", null)).hasMessage("Not found");
|
|
|
|
|
assertThatThrownBy(() -> RecentsService.recordView("RECORD", "ledger", "7")).hasMessage("Not found");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*******************************************************************************
|
|
|
|
|
** A checker of the kind hosts and other qbits write for read-only tables: QQQ
|
|
|
|
|
** hands it the action input and not the permission asked, so it reads the
|
|
|
|
|
** input's type. A query, a get and a count need its permission; every other
|
|
|
|
|
** input is refused to everyone.
|
|
|
|
|
*******************************************************************************/
|
|
|
|
|
public static class ReadsByInputType implements CustomPermissionChecker
|
|
|
|
|
{
|
|
|
|
|
static final String PERMISSION = "ledger.view";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*******************************************************************************
|
|
|
|
|
** Refuse whatever is not a recognised read, then require the permission.
|
|
|
|
|
*******************************************************************************/
|
|
|
|
|
@Override
|
|
|
|
|
public void checkPermissionsThrowing(AbstractActionInput actionInput, MetaDataWithPermissionRules metaDataWithPermissionRules) throws QPermissionDeniedException
|
|
|
|
|
{
|
|
|
|
|
if(!(actionInput instanceof QueryInput || actionInput instanceof GetInput || actionInput instanceof CountInput))
|
|
|
|
|
{
|
|
|
|
|
throw (new QPermissionDeniedException("This table is read-only."));
|
|
|
|
|
}
|
|
|
|
|
if(!QContext.getQSession().hasPermission(PERMISSION))
|
|
|
|
|
{
|
|
|
|
|
throw (new QPermissionDeniedException("Permission denied."));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*******************************************************************************
|
|
|
|
|
** Newest-first ordering is stable; locked rows are dropped before the limit.
|
|
|
|
|
*******************************************************************************/
|
|
|
|
|