/**
* Copyright (C) 2010-2017 Structr GmbH
*
* This file is part of Structr <http://structr.org>.
*
* Structr 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.
*
* Structr 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 Structr. If not, see <http://www.gnu.org/licenses/>.
*/
package org.structr.web.basic;
import org.structr.web.StructrUiTest;
import com.jayway.restassured.RestAssured;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.structr.common.error.FrameworkException;
import org.structr.core.GraphObject;
import org.structr.core.app.App;
import org.structr.core.app.StructrApp;
import org.structr.core.entity.AbstractNode;
import org.structr.core.entity.ResourceAccess;
import org.structr.core.graph.Tx;
import org.structr.core.property.PropertyMap;
import org.structr.web.auth.UiAuthenticator;
import org.structr.web.entity.Folder;
import org.structr.web.entity.User;
//~--- classes ----------------------------------------------------------------
/**
* Test resource access security implemented in {@link UiAuthenticator}
*
*
*/
public class ResourceAccessTest extends StructrUiTest {
private static final Logger logger = LoggerFactory.getLogger(ResourceAccessTest.class.getName());
@Test
public void test01ResourceAccessGET() {
// clear resource access objects that are created by the dynamic schema
clearResourceAccess();
Folder testFolder = null;
ResourceAccess folderGrant = null;
try (final Tx tx = app.tx()) {
testFolder = createTestNodes(Folder.class, 1).get(0);
assertNotNull(testFolder);
// no resource access node at all => forbidden
RestAssured.given().contentType("application/json; charset=UTF-8").expect().statusCode(401).when().get("/folders");
tx.success();
} catch (FrameworkException fex) {
logger.warn("", fex);
logger.error(fex.toString());
fail("Unexpected exception");
}
try (final Tx tx = app.tx()) {
folderGrant = createResourceAccess("Folder", UiAuthenticator.FORBIDDEN);
tx.success();
} catch (FrameworkException fex) {
logger.warn("", fex);
logger.error(fex.toString());
fail("Unexpected exception");
}
try (final Tx tx = app.tx()) {
// resource access explicetly set to FORBIDDEN => forbidden
RestAssured.given().contentType("application/json; charset=UTF-8").expect().statusCode(401).when().get("/folders");
// allow GET for authenticated users => access without user/pass should be still forbidden
folderGrant.setProperties(folderGrant.getSecurityContext(), new PropertyMap(GraphObject.visibleToPublicUsers, true));
folderGrant.setFlag(UiAuthenticator.AUTH_USER_GET);
tx.success();
} catch (FrameworkException fex) {
logger.warn("", fex);
logger.error(fex.toString());
fail("Unexpected exception");
}
try (final Tx tx = app.tx()) {
RestAssured.given().contentType("application/json; charset=UTF-8").expect().statusCode(401).when().get("/folders");
// allow GET for non-authenticated users => access without user/pass should be allowed
folderGrant.setFlag(UiAuthenticator.NON_AUTH_USER_GET);
tx.success();
} catch (FrameworkException fex) {
logger.warn("", fex);
logger.error(fex.toString());
fail("Unexpected exception");
}
try (final Tx tx = app.tx()) {
RestAssured.given().contentType("application/json; charset=UTF-8").expect().statusCode(200).when().get("/folders");
tx.success();
} catch (FrameworkException fex) {
logger.warn("", fex);
logger.error(fex.toString());
fail("Unexpected exception");
}
}
@Test
public void test02ResourceAccessPOST() {
// clear resource access objects that are created by the dynamic schema
clearResourceAccess();
ResourceAccess folderGrant = null;
try (final Tx tx = app.tx()) {
// no resource access node at all => forbidden
RestAssured.given().contentType("application/json; charset=UTF-8").expect().statusCode(401).when().post("/folders");
folderGrant = createResourceAccess("Folder", UiAuthenticator.FORBIDDEN);
// resource access explicetly set to FORBIDDEN => forbidden
RestAssured.given().contentType("application/json; charset=UTF-8").expect().statusCode(401).when().post("/folders");
// allow POST for authenticated users => access without user/pass should be still forbidden
folderGrant.setFlag(UiAuthenticator.AUTH_USER_POST);
tx.success();
} catch (FrameworkException fex) {
logger.warn("", fex);
logger.error(fex.toString());
fail("Unexpected exception");
}
try (final Tx tx = app.tx()) {
RestAssured.given().contentType("application/json; charset=UTF-8").expect().statusCode(401).when().post("/folders");
// allow POST for non-authenticated users => access without user/pass should be allowed
folderGrant.setProperties(folderGrant.getSecurityContext(), new PropertyMap(GraphObject.visibleToPublicUsers, true));
folderGrant.setFlag(UiAuthenticator.NON_AUTH_USER_POST);
tx.success();
} catch (FrameworkException fex) {
logger.warn("", fex);
logger.error(fex.toString());
fail("Unexpected exception");
}
try (final Tx tx = app.tx()) {
RestAssured.given().contentType("application/json; charset=UTF-8").body("{'name':'Test01'}").expect().statusCode(201).when().post("/folders");
tx.success();
} catch (FrameworkException ex) {
logger.warn("", ex);
logger.error(ex.toString());
fail("Unexpected exception");
}
}
@Test
public void test03ResourceAccessPUT() {
// clear resource access objects that are created by the dynamic schema
clearResourceAccess();
final String name = "testuser-01";
final String password = "testpassword-01";
ResourceAccess folderGrant = null;
User testUser = null;
Folder testFolder = null;
try (final Tx tx = app.tx()) {
testUser = createTestNodes(User.class, 1).get(0);
testFolder = createTestNodes(Folder.class, 1).get(0);
assertNotNull(testFolder);
// no resource access node at all => forbidden
RestAssured.given().contentType("application/json; charset=UTF-8").expect().statusCode(401).when().put("/folder/" + testFolder.getUuid());
folderGrant = createResourceAccess("Folder", UiAuthenticator.FORBIDDEN);
tx.success();
} catch (FrameworkException fex) {
logger.warn("", fex);
logger.error(fex.toString());
fail("Unexpected exception");
}
try (final Tx tx = app.tx()) {
// resource access explicitly set to FORBIDDEN => forbidden
RestAssured.given().contentType("application/json; charset=UTF-8").expect().statusCode(401).when().put("/folder/" + testFolder.getUuid());
// allow PUT for authenticated users => access without user/pass should be still forbidden
folderGrant.setFlag(UiAuthenticator.AUTH_USER_PUT);
tx.success();
} catch (FrameworkException fex) {
logger.warn("", fex);
logger.error(fex.toString());
fail("Unexpected exception");
}
try (final Tx tx = app.tx()) {
RestAssured.given().contentType("application/json; charset=UTF-8").expect().statusCode(401).when().put("/folder/" + testFolder.getUuid());
// allow PUT for non-authenticated users =>
folderGrant.setProperties(folderGrant.getSecurityContext(), new PropertyMap(GraphObject.visibleToPublicUsers, true));
folderGrant.setFlag(UiAuthenticator.NON_AUTH_USER_PUT);
tx.success();
} catch (FrameworkException fex) {
logger.warn("", fex);
logger.error(fex.toString());
fail("Unexpected exception");
}
try (final Tx tx = app.tx()) {
// ownerless non-public node cannot be found by anonymous user
RestAssured.given().contentType("application/json; charset=UTF-8").expect().statusCode(404).when().put("/folder/" + testFolder.getUuid());
// Prepare for next test
final PropertyMap testUserProperties = new PropertyMap();
testUserProperties.put(AbstractNode.name, name);
testUserProperties.put(User.password, password);
testUser.setProperties(testUser.getSecurityContext(), testUserProperties);
// now we give the user ownership and expect a 200
testFolder.setProperties(testFolder.getSecurityContext(), new PropertyMap(AbstractNode.owner, testUser));
tx.success();
} catch (FrameworkException fex) {
logger.warn("", fex);
logger.error(fex.toString());
fail("Unexpected exception");
}
try (final Tx tx = app.tx()) {
RestAssured.given()
.headers("X-User", name, "X-Password", password)
.contentType("application/json; charset=UTF-8").expect().statusCode(200).when().put("/folder/" + testFolder.getUuid());
tx.success();
} catch (FrameworkException ex) {
logger.warn("", ex);
logger.error(ex.toString());
fail("Unexpected exception");
}
}
@Test
public void test04ResourceAccessDELETE() {
// clear resource access objects that are created by the dynamic schema
clearResourceAccess();
final String name = "testuser-01";
final String password = "testpassword-01";
Folder testFolder = null;
User testUser = null;
ResourceAccess folderGrant = null;
try (final Tx tx = app.tx()) {
testFolder = createTestNodes(Folder.class, 1).get(0);
assertNotNull(testFolder);
testUser = createTestNodes(User.class, 1).get(0);
tx.success();
} catch (FrameworkException fex) {
logger.warn("", fex);
logger.error(fex.toString());
fail("Unexpected exception");
}
try (final Tx tx = app.tx()) {
// no resource access node at all => forbidden
RestAssured.given().contentType("application/json; charset=UTF-8").expect().statusCode(401).when().delete("/folder/" + testFolder.getUuid());
folderGrant = createResourceAccess("Folder", UiAuthenticator.FORBIDDEN);
tx.success();
} catch (FrameworkException fex) {
logger.warn("", fex);
logger.error(fex.toString());
fail("Unexpected exception");
}
try (final Tx tx = app.tx()) {
// resource access explicitly set to FORBIDDEN => forbidden
RestAssured.given().contentType("application/json; charset=UTF-8").expect().statusCode(401).when().delete("/folder/" + testFolder.getUuid());
folderGrant.setFlag(UiAuthenticator.AUTH_USER_DELETE);
tx.success();
} catch (FrameworkException fex) {
logger.warn("", fex);
logger.error(fex.toString());
fail("Unexpected exception");
}
try (final Tx tx = app.tx()) {
RestAssured.given().contentType("application/json; charset=UTF-8").expect().statusCode(401).when().delete("/folder/" + testFolder.getUuid());
folderGrant.setProperties(folderGrant.getSecurityContext(), new PropertyMap(GraphObject.visibleToPublicUsers, true));
folderGrant.setFlag(UiAuthenticator.NON_AUTH_USER_DELETE);
tx.success();
} catch (FrameworkException fex) {
logger.warn("", fex);
logger.error(fex.toString());
fail("Unexpected exception");
}
try (final Tx tx = app.tx()) {
RestAssured.given().contentType("application/json; charset=UTF-8").expect().statusCode(404).when().delete("/folder/" + testFolder.getUuid());
final PropertyMap changedProperties = new PropertyMap();
changedProperties.put(AbstractNode.name, name);
changedProperties.put(User.password, password);
testUser.setProperties(testUser.getSecurityContext(), changedProperties);
// make user own folder
testFolder.setProperties(testFolder.getSecurityContext(), new PropertyMap(AbstractNode.owner, testUser));
tx.success();
} catch (FrameworkException fex) {
logger.warn("", fex);
logger.error(fex.toString());
fail("Unexpected exception");
}
try (final Tx tx = app.tx()) {
// test user owns object now => 200
RestAssured.given()
.headers("X-User", name, "X-Password", password)
.contentType("application/json; charset=UTF-8").expect().statusCode(200).when().delete("/folder/" + testFolder.getUuid());
tx.success();
} catch (FrameworkException fex) {
logger.warn("", fex);
logger.error(fex.toString());
fail("Unexpected exception");
}
}
/**
* Creates a new ResourceAccess entity with the given signature and
* flags in the database.
*
* @param signature the name of the new page, defaults to "page" if not
* set
* @param flags
*
* @return the new resource access node
* @throws FrameworkException
*/
public static ResourceAccess createResourceAccess(String signature, long flags) throws FrameworkException {
final PropertyMap properties = new PropertyMap();
final App app = StructrApp.getInstance();
properties.put(ResourceAccess.signature, signature);
properties.put(ResourceAccess.flags, flags);
try {
ResourceAccess access = app.create(ResourceAccess.class, properties);
return access;
} catch (Throwable t) {
logger.warn("", t);
}
return null;
}
public static void clearResourceAccess() {
final App app = StructrApp.getInstance();
try (final Tx tx = app.tx()) {
for (final ResourceAccess access : app.nodeQuery(ResourceAccess.class).getAsList()) {
app.delete(access);
}
tx.success();
} catch (Throwable t) {
logger.warn("", t);
}
}
}