Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#25587 first draft for scripting role on secret tool #25588

Merged
merged 8 commits into from
Sep 14, 2023
1 change: 1 addition & 0 deletions cicd/docker/dotcms-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ services:
WAIT_FOR_DEPS:
JVM_ENDPOINT_TEST_PASS:
DOT_ANALYTICS_IDP_URL: http://localhost:61111/realms/dotcms/protocol/openid-connect/token
DOT_ENABLE_SCRIPTING: "true"
depends_on:
- database
- elasticsearch
Expand Down
129 changes: 125 additions & 4 deletions dotCMS/src/curl-test/VelocitySecrets.postman_collection.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,68 @@
{
"info": {
"_postman_id": "c55a23bc-a33b-42ab-a63b-083e43af4572",
"_postman_id": "e2ac4397-5a79-464d-a0d9-243f72420eff",
"name": "VelocitySecrets",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "781456"
},
"item": [
{
"name": "CreateNewScriptingUser",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Status code should be 200\", function () {",
" pm.response.to.have.status(200);",
"});",
""
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "admin",
"type": "string"
},
{
"key": "username",
"value": "[email protected]",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"firstName\":\"Scripting User\",\n \"lastName\":\"Scripting User\",\n \"email\":\"[email protected]\",\n \"password\":[\"d\",\"o\",\"t\",\"c\",\"m\",\"s\",\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\"],\n \"roles\":[\"DOTCMS_BACK_END_USER\",\"Scripting Developer\"]\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/v1/users",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"users"
]
}
},
"response": []
},
{
"name": "Add Config to System Host",
"event": [
Expand Down Expand Up @@ -125,7 +182,39 @@
"response": []
},
{
"name": "Test Velocity Secrets",
"name": "invalidateSession",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{serverURL}}/api/v1/logout",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"logout"
]
}
},
"response": []
},
{
"name": "Test Velocity Secrets With Scripting User",
"event": [
{
"listen": "test",
Expand Down Expand Up @@ -177,12 +266,12 @@
"basic": [
{
"key": "password",
"value": "admin",
"value": "dotcms12345678",
"type": "string"
},
{
"key": "username",
"value": "admin@dotcms.com",
"value": "scripting@dotcms.com",
"type": "string"
}
]
Expand Down Expand Up @@ -212,6 +301,38 @@
}
},
"response": []
},
{
"name": "invalidateSessionAgain",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Status code is 200\", function () {",
" pm.response.to.have.status(200);",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{serverURL}}/api/v1/logout",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"logout"
]
}
},
"response": []
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,21 @@
import com.dotcms.api.web.HttpServletRequestThreadLocal;
import com.dotcms.rendering.velocity.viewtools.secrets.DotVelocitySecretAppConfig;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.business.Role;
import com.dotmarketing.business.UserAPI;
import com.dotmarketing.business.web.WebAPILocator;
import com.dotmarketing.portlets.contentlet.model.Contentlet;
import com.dotmarketing.util.Config;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.UtilMethods;
import com.liferay.portal.model.User;
import org.apache.velocity.context.Context;
import org.apache.velocity.context.InternalContextAdapterImpl;
import org.apache.velocity.tools.view.context.ViewContext;
import org.apache.velocity.tools.view.tools.ViewTool;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Optional;

/**
Expand All @@ -15,8 +27,16 @@
*/
public class SecretTool implements ViewTool {

private InternalContextAdapterImpl internalContextAdapter;
private Context context;
private HttpServletRequest request;

@Override
public void init(Object initData) {
public void init(final Object initData) {

final ViewContext context = (ViewContext) initData;
this.request = context.getRequest();
this.context = context.getVelocityContext();
}

/**
Expand All @@ -26,6 +46,8 @@ public void init(Object initData) {
*/
public Object get(final String key) {

canUserEvaluate();

final HttpServletRequest request = HttpServletRequestThreadLocal.INSTANCE.getRequest();
final Optional<DotVelocitySecretAppConfig> config = DotVelocitySecretAppConfig.config(request);
return config.isPresent()? config.get().getStringOrNull(key) : null;
Expand All @@ -51,12 +73,14 @@ public Object getSystemSecret (final String key) {
public Object getSystemSecret (final String key,
final Object defaultValue) {

canUserEvaluate();
final Optional<DotVelocitySecretAppConfig> config = DotVelocitySecretAppConfig.config(APILocator.systemHost());
return config.isPresent()? config.get().getStringOrNull(key, null!= defaultValue? defaultValue.toString():null) : defaultValue;
}

public char[] getCharArray(final String key) {

canUserEvaluate();
final HttpServletRequest request = HttpServletRequestThreadLocal.INSTANCE.getRequest();
final Optional<DotVelocitySecretAppConfig> config = DotVelocitySecretAppConfig.config(request);
return config.isPresent()? config.get().getCharArrayOrNull(key) : null;
Expand All @@ -70,7 +94,58 @@ public char[] getCharArraySystemSecret (final String key) {
public char[] getCharArraySystemSecret (final String key,
final char[] defaultValue) {

canUserEvaluate();
final Optional<DotVelocitySecretAppConfig> config = DotVelocitySecretAppConfig.config(APILocator.systemHost());
return config.isPresent()? config.get().getCharArrayOrNull(key, defaultValue) : defaultValue;
}

private static final boolean ENABLE_SCRIPTING = Config.getBooleanProperty("ENABLE_SCRIPTING", false);

/**
* Test 2 things.
* 1) see if the user has the scripting role
* 2) otherwise check if the last modified user has the scripting role
* @return boolean
*/
protected void canUserEvaluate() {

if(!ENABLE_SCRIPTING) {

Logger.warn(this.getClass(), "Scripting called and ENABLE_SCRIPTING set to false");
throw new SecurityException("External scripting is disabled in your dotcms instance.");
}

try {

boolean hasScriptingRole = false;
final Role scripting = APILocator.getRoleAPI().loadRoleByKey(Role.SCRIPTING_DEVELOPER);

this.internalContextAdapter = new InternalContextAdapterImpl(context);
final String fieldResourceName = this.internalContextAdapter.getCurrentTemplateName();
if (UtilMethods.isSet(fieldResourceName)) {
final String contentletFileAssetInode = fieldResourceName.substring(fieldResourceName.indexOf("/") + 1, fieldResourceName.indexOf("_"));
final Contentlet contentlet = APILocator.getContentletAPI().find(contentletFileAssetInode, APILocator.systemUser(), true);
final User lastModifiedUser = APILocator.getUserAPI().loadUserById(contentlet.getModUser(), APILocator.systemUser(), true);
hasScriptingRole = APILocator.getRoleAPI().doesUserHaveRole(lastModifiedUser, scripting);
}

if (!hasScriptingRole) {
final User user = WebAPILocator.getUserWebAPI().getUser(this.request);
// try with the current user
if (null != user) {

hasScriptingRole = APILocator.getRoleAPI().doesUserHaveRole(user, scripting);
}
}

if (!hasScriptingRole) {

throw new SecurityException("External scripting is disabled in your dotcms instance.");
}
} catch(Exception e) {

Logger.warn(this.getClass(), "Scripting called with error" + e);
throw new SecurityException("External scripting is disabled in your dotcms instance.", e);
}
} // canUserEvaluate.
}
2 changes: 2 additions & 0 deletions dotCMS/src/main/java/com/dotmarketing/business/Role.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public class Role implements Serializable,Comparable<Role> {
public static final String CMS_OWNER_ROLE = "CMS Owner";
public static final String CMS_ADMINISTRATOR_ROLE = "CMS Administrator";

public static final String SCRIPTING_DEVELOPER = "Scripting Developer";



public static final String DOTCMS_BACK_END_USER = "DOTCMS_BACK_END_USER";
Expand Down
2 changes: 1 addition & 1 deletion dotCMS/src/main/webapp/WEB-INF/toolbox.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
</tool>
<tool>
<key>dotsecrets</key>
<scope>application</scope>
<scope>request</scope>
<class>com.dotcms.rendering.velocity.viewtools.SecretTool</class>
</tool>
<tool>
Expand Down