Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,22 @@ public enum ModelAuthorizationAction implements DisplayableValue<String> {

READ_THREADS("readThreads", "Read threads", "READ_THREADS_HELP"),

/**
* Ability to read the content of the midPoint log file, and to determine its size.
*
* Covers both {@link ModelDiagnosticService#getLogFileContent(Long, Long, Task, OperationResult)} and
* {@link ModelDiagnosticService#getLogFileSize(Task, OperationResult)}. The two are not separated, because
* the size is strictly less information than the content, and clients are expected to use them together
* (see the `ReturnedDataPosition` / `CurrentLogFileSize` headers of the `/log` REST operation).
*
* Previously, these operations required the `authorization-3#all` authorization, i.e. effectively a superuser.
* Existing superusers are not affected by the change, as `#all` is applicable to any action.
*
* @see RestAuthorizationAction#GET_LOG
* @see RestAuthorizationAction#GET_LOG_SIZE
*/
READ_LOG("readLog", "Read log", "READ_LOG_HELP"),

/** Ability to complete a work item (case- or certification- related). */
COMPLETE_WORK_ITEM("completeWorkItem", "Complete work item", "COMPLETE_WORK_ITEM_HELP"),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,21 @@ String exportDataModel(ResourceType resource, DataModelVisualizer.Target target,
/**
* Returns the contents of the log file.
*
* Requires the {@link ModelAuthorizationAction#READ_LOG} authorization.
* (Before, the `authorization-3#all` one - i.e. effectively a superuser - was required.)
*
* @param fromPosition From absolute log file position (if non-negative); or counted from the end (if negative).
* @param maxSize Max number of bytes to return.
* @param task
* @param parentResult
*/
LogFileContentType getLogFileContent(Long fromPosition, Long maxSize, Task task, OperationResult parentResult)
throws SecurityViolationException, IOException, SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException;

/**
* Returns the size of the log file, in bytes.
*
* Requires the {@link ModelAuthorizationAction#READ_LOG} authorization.
* (Before, the `authorization-3#all` one - i.e. effectively a superuser - was required.)
*/
long getLogFileSize(Task task, OperationResult parentResult) throws SchemaException, SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, ConfigurationException, CommunicationException;

// change the return type eventually
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.evolveum.midpoint.common.configuration.api.MidpointConfiguration;
import com.evolveum.midpoint.init.SystemUtil;
import com.evolveum.midpoint.model.api.DataModelVisualizer;
import com.evolveum.midpoint.model.api.ModelAuthorizationAction;
import com.evolveum.midpoint.model.api.ModelDiagnosticService;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
Expand Down Expand Up @@ -596,12 +597,20 @@ public String exportDataModel(ResourceType resource, DataModelVisualizer.Target
}
}

/**
* Note about authorizations: This method (and {@link #getLogFileSize(Task, OperationResult)}) is covered by the
* {@link ModelAuthorizationAction#READ_LOG} authorization. To avoid the need of having `rest-3#all` authorization
* to use the corresponding REST methods, there are also special (much more specific, i.e. weaker) REST
* authorizations: `rest-3#getLog` and `rest-3#getLogSize` (see `RestAuthorizationAction`).
* So, a user reading the log needs just these (rather weak) authorizations, instead of the `authorization-3#all`
* one that was required before.
*/
@Override
public LogFileContentType getLogFileContent(Long fromPosition, Long maxSize, Task task, OperationResult parentResult)
throws SecurityViolationException, IOException, SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException {
OperationResult result = parentResult.createSubresult(GET_LOG_FILE_CONTENT);
try {
securityEnforcer.authorizeAll(task, result);
securityEnforcer.authorize(ModelAuthorizationAction.READ_LOG.getUrl(), task, result);
File logFile = getLogFile(result);
LogFileContentType rv = getLogFileFragment(logFile, fromPosition, maxSize);
result.recordSuccess();
Expand Down Expand Up @@ -653,7 +662,7 @@ public long getLogFileSize(Task task, OperationResult parentResult)
ConfigurationException, CommunicationException {
OperationResult result = parentResult.createSubresult(GET_LOG_FILE_SIZE);
try {
securityEnforcer.authorizeAll(task, result);
securityEnforcer.authorize(ModelAuthorizationAction.READ_LOG.getUrl(), task, result);
File logFile = getLogFile(result);
long size = logFile.length();
result.recordSuccess();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ public enum RestAuthorizationAction implements DisplayableValue<String> {
RUN_TASK("runTask", "Run task", "RUN_TASK_HELP"),
EXECUTE_SCRIPT("executeScript", "Execute script", "EXECUTE_SCRIPT_HELP"),
COMPARE_OBJECT("compareObject", "Compare object", "COMPARE_OBJECT_HELP"),
/** Besides this one, the `authorization-model-3#readLog` action is required as well. */
GET_LOG_SIZE("getLogSize", "Get log size", "GET_LOG_SIZE_HELP"),
/** Besides this one, the `authorization-model-3#readLog` action is required as well. */
GET_LOG("getLog", "Get log", "GET_LOG_HELP"),
RESET_CREDENTIAL("resetCredential", "Reset credential", "RESET_CREDENTIAL_HELP"),
GET_THREADS("getThreads", "Get threads", "GET_THREADS_HELP"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,15 @@ public abstract class AbstractRestServiceInitializer extends AbstractGuiIntegrat
public static final File USER_REST_LIMITED_FILE = new File(BASE_REPO_DIR, "user-rest-limited.xml");
public static final String USER_REST_LIMITED_NAME = "rest-limited";
public static final String USER_REST_LIMITED_PASSWORD = "r3stL1m1t3d";
public static final File USER_REST_LOG_FILE = new File(BASE_REPO_DIR, "user-rest-log.xml");
public static final String USER_REST_LOG_NAME = "rest-log";
public static final String USER_REST_LOG_PASSWORD = "r3stL0gR34d";

public static final File ROLE_SUPERUSER_FILE = new File(BASE_REPO_DIR, "role-superuser.xml");
public static final File ROLE_ENDUSER_FILE = new File(BASE_REPO_DIR, "role-enduser.xml");
public static final File ROLE_REST_FILE = new File(BASE_REPO_DIR, "role-rest.xml");
public static final File ROLE_REST_LIMITED_FILE = new File(BASE_REPO_DIR, "role-rest-limited.xml");
public static final File ROLE_REST_LOG_FILE = new File(BASE_REPO_DIR, "role-rest-log.xml");
public static final File ROLE_READER_FILE = new File(BASE_REPO_DIR, "role-reader.xml");

public static final File SYSTEM_CONFIGURATION_FILE = new File(BASE_REPO_DIR, "system-configuration.xml");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public void initSystem(Task initTask, OperationResult result) throws Exception {
addObject(endRole, executeOptions().overwrite(), initTask, result);
addObject(ROLE_REST_FILE, initTask, result);
addObject(ROLE_REST_LIMITED_FILE, initTask, result);
addObject(ROLE_REST_LOG_FILE, initTask, result);
addObject(ROLE_READER_FILE, initTask, result);
PrismObject<UserType> adminUser = parseObject(USER_ADMINISTRATOR_FILE);
addObject(adminUser, executeOptions().overwrite(), initTask, result);
Expand All @@ -38,6 +39,7 @@ public void initSystem(Task initTask, OperationResult result) throws Exception {
addObject(USER_SOMEBODY_FILE, initTask, result);
addObject(USER_JACK_FILE, initTask, result);
addObject(USER_REST_LIMITED_FILE, initTask, result);
addObject(USER_REST_LOG_FILE, initTask, result);
addObject(parseObject(VALUE_POLICY_GENERAL), executeOptions().overwrite(), initTask, result);
addObject(VALUE_POLICY_NUMERIC, initTask, result);
addObject(VALUE_POLICY_SIMPLE, initTask, result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1661,6 +1661,34 @@ public void test690RestMethodNotAuthorized() {
getDummyAuditService().assertFailedLogin(SchemaConstants.CHANNEL_REST_URI);
}

/* User has REST autz for the log operations, but not the model-level "readLog" one. So this fails. */
@Test
public void test691GetLogNotAuthorizedAtModelLevel() {
WebClient client = prepareClient(USER_REST_LIMITED_NAME, USER_REST_LIMITED_PASSWORD);
client.path("/log");

when();
Response response = client.get();

then();
displayResponse(response);
assertStatus(response, 403);
}

/* The same for the "log size" operation. */
@Test
public void test692GetLogSizeNotAuthorizedAtModelLevel() {
WebClient client = prepareClient(USER_REST_LIMITED_NAME, USER_REST_LIMITED_PASSWORD);
client.path("/log/size");

when();
Response response = client.get();

then();
displayResponse(response);
assertStatus(response, 403);
}

/* User has both REST and model autz for "get object" operation. So this succeeds. */
@Test
public void test695RestMethodAuthorized() {
Expand All @@ -1675,6 +1703,53 @@ public void test695RestMethodAuthorized() {
assertStatus(response, 200);
}

/* User has both the REST and the model-level "readLog" autz - and is not a superuser. So this succeeds. */
@Test
public void test696GetLogAuthorized() {
WebClient client = prepareClient(USER_REST_LOG_NAME, USER_REST_LOG_PASSWORD);
client.path("/log");
client.query("maxSize", 1024);

when();
Response response = client.get();

then();
displayResponse(response);
assertStatus(response, 200);
assertNotNull("No CurrentLogFileSize header", response.getHeaderString("CurrentLogFileSize"));
assertNotNull("No log content returned", response.readEntity(String.class));
}

/* The same for the "log size" operation. */
@Test
public void test697GetLogSizeAuthorized() {
WebClient client = prepareClient(USER_REST_LOG_NAME, USER_REST_LOG_PASSWORD);
client.path("/log/size");

when();
Response response = client.get();

then();
displayResponse(response);
assertStatus(response, 200);
long size = Long.parseLong(response.readEntity(String.class).trim());
AssertJUnit.assertTrue("Log file size is not positive: " + size, size > 0);
}

/* Superuser has the "all" authorization, which is applicable to any action. So this still succeeds. */
@Test
public void test698GetLogSizeAsSuperuser() {
WebClient client = prepareClient();
client.path("/log/size");

when();
Response response = client.get();

then();
displayResponse(response);
assertStatus(response, 200);
}

private WebClient prepareClient() {
return prepareClient(USER_ADMINISTRATOR_USERNAME, USER_ADMINISTRATOR_PASSWORD);
}
Expand Down
8 changes: 8 additions & 0 deletions testing/rest/src/test/resources/repo/role-rest-limited.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,12 @@
<description>This one can be applied.</description>
<action>http://midpoint.evolveum.com/xml/ns/public/security/authorization-model-3#read</action>
</authorization>
<authorization>
<description>
The REST-level authorizations for the log operations are present, but the model-level "readLog"
one is not. Hence, the operations pass the REST layer and fail at the model layer.
</description>
<action>http://midpoint.evolveum.com/xml/ns/public/security/authorization-rest-3#getLog</action>
<action>http://midpoint.evolveum.com/xml/ns/public/security/authorization-rest-3#getLogSize</action>
</authorization>
</role>
27 changes: 27 additions & 0 deletions testing/rest/src/test/resources/repo/role-rest-log.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2010-2026 Evolveum and contributors
~
~ Licensed under the EUPL-1.2 or later.
-->

<role oid="0c1c5d2a-2b0f-4a2a-9e5f-1b6c2f8e4a11"
xmlns="http://midpoint.evolveum.com/xml/ns/public/common/common-3">
<name>rest-log</name>
<description>
Reading of the log file, without any "all" authorization. This is what a read-only administrator
needs in order to read the log via REST.
</description>
<authorization>
<description>REST-level authorizations for the log operations.</description>
<action>http://midpoint.evolveum.com/xml/ns/public/security/authorization-rest-3#getLog</action>
<action>http://midpoint.evolveum.com/xml/ns/public/security/authorization-rest-3#getLogSize</action>
</authorization>
<authorization>
<description>
Model-level authorization to read the log. Together with the REST authorizations above this is
sufficient; no "all" authorization is needed.
</description>
<action>http://midpoint.evolveum.com/xml/ns/public/security/authorization-model-3#readLog</action>
</authorization>
</role>
25 changes: 25 additions & 0 deletions testing/rest/src/test/resources/repo/user-rest-log.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2010-2026 Evolveum and contributors
~
~ Licensed under the EUPL-1.2 or later.
-->

<user oid="3f8b1c74-9a6d-4f0e-b0a1-7d2e5c9b3a55"
xmlns="http://midpoint.evolveum.com/xml/ns/public/common/common-3">
<name>rest-log</name>
<assignment>
<targetRef oid="0c1c5d2a-2b0f-4a2a-9e5f-1b6c2f8e4a11" type="RoleType"/>
</assignment>
<credentials>
<password>
<value>r3stL0gR34d</value>
</password>
<securityQuestions>
<questionAnswer>
<questionIdentifier>http://midpoint.evolveum.com/xml/ns/public/security/question-2#q001</questionIdentifier>
<questionAnswer>r3stL0gR34d</questionAnswer>
</questionAnswer>
</securityQuestions>
</credentials>
</user>