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

Feature #535: Should respond error when accessed via domain name different from the one configured in Unit Certificate #560

9 changes: 9 additions & 0 deletions src/main/java/io/personium/core/PersoniumCoreException.java
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,14 @@ public static class Common {
* {0} : Parse string
*/
public static final PersoniumCoreException JSON_PARSE_ERROR = create("PR400-CM-0004");
/**
* Invalid URL authority.
* <p>
* {0} : Given authority
* {1} : Configured authority
*/
public static final PersoniumCoreException INVALID_URL_AUTHORITY = create("PR400-CM-0005");
shimono marked this conversation as resolved.
Show resolved Hide resolved

/**
* Executing API that is not allowed when the cell status is "import failed".
*/
Expand Down Expand Up @@ -1263,3 +1271,4 @@ private static void logCauseChain(final Logger log, Throwable cause) {
}
}
}

15 changes: 15 additions & 0 deletions src/main/java/io/personium/core/PersoniumUnitConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -869,6 +869,21 @@ public static int getUnitPort() {
}
return port;
}
/**
* Returns the URL authority of the Unit.
* For path-based cell URL mode, this will be the only allowable authority.
* For sub-domain-based cell URL mode, this and its sub-domain FQDN will be allowed.
* @return URL authority of the Unit.
*/
public static String getUnitAuthority() {
String ret = CommonUtils.getFQDN();
int p = getUnitPort();
if (p == -1) {
return ret;
}
ret = ret + ":" + p;
return ret;
}

/**
* URL format to access cell.
Expand Down
4 changes: 1 addition & 3 deletions src/main/java/io/personium/core/model/ModelFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import io.personium.core.model.impl.fs.CellSnapshotCellCmpFsImpl;

/**
* Factory class of model object.
* Factory class for model objects.
*/
public final class ModelFactory {
/**
Expand Down Expand Up @@ -150,7 +150,5 @@ public static UserSchemaODataProducer userSchema(final Cell cell, final DavCmp d
public static UserDataODataProducer userData(final Cell cell, final DavCmp davCmp) {
return new UserDataODataProducer(cell, davCmp);
}

}

}
52 changes: 31 additions & 21 deletions src/main/java/io/personium/core/rs/FacadeResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,36 +102,46 @@ public Object facade(
PersoniumCoreLog.Server.REQUEST_KEY.params("received", headerPersoniumRequestKey).writeLog();
}

if (PersoniumUnitConfig.isPathBasedCellUrlEnabled()) {
String accessUrlAuthority = uriInfo.getBaseUri().getAuthority();
String unitAuthority = PersoniumUnitConfig.getUnitAuthority();

// if URL authority matches the config then OK.
if (unitAuthority.equals(accessUrlAuthority)) {
return new UnitResource(cookieAuthValue, cookiePeer, headerAuthz, headerHost,
headerPersoniumUnitUser, uriInfo);
}

String accessUrl = uriInfo.getBaseUri().toString();
String configUrl = PersoniumUnitConfig.getBaseUrl();
if (configUrl.equals(accessUrl)) {
return new UnitResource(cookieAuthValue, cookiePeer, headerAuthz, headerHost,
headerPersoniumUnitUser, uriInfo);
if (PersoniumUnitConfig.isPathBasedCellUrlEnabled()) {
// if path based, then respond error since unitAuthority itself is the only allowable authority.
throw PersoniumCoreException.Common.INVALID_URL_AUTHORITY.params(accessUrlAuthority, unitAuthority);
} else {
// {CellName}.{FQDN} access
// if sub-domain based, then ..
String cellName = headerHost.split("\\.")[0];
Cell cell = ModelFactory.cellFromName(cellName);
if (cell == null) {
throw PersoniumCoreException.Dav.CELL_NOT_FOUND;
}
AccessContext ac = AccessContext.create(headerAuthz, uriInfo, cookiePeer, cookieAuthValue, cell,
uriInfo.getBaseUri().toString(), headerHost, headerPersoniumUnitUser);
// subdomain case is also fine
if (accessUrlAuthority.equals(cellName + "." + unitAuthority)) {
Cell cell = ModelFactory.cellFromName(cellName);
if (cell == null) {
throw PersoniumCoreException.Dav.CELL_NOT_FOUND;
}
AccessContext ac = AccessContext.create(headerAuthz, uriInfo, cookiePeer, cookieAuthValue, cell,
uriInfo.getBaseUri().toString(), headerHost, headerPersoniumUnitUser);

CellLockManager.STATUS cellStatus = CellLockManager.getCellStatus(cell.getId());
if (CellLockManager.STATUS.BULK_DELETION.equals(cellStatus)) {
throw PersoniumCoreException.Dav.CELL_NOT_FOUND;
}
CellLockManager.STATUS cellStatus = CellLockManager.getCellStatus(cell.getId());
if (CellLockManager.STATUS.BULK_DELETION.equals(cellStatus)) {
throw PersoniumCoreException.Dav.CELL_NOT_FOUND;
}

CellLockManager.incrementReferenceCount(cell.getId());
httpServletRequest.setAttribute("cellId", cell.getId());
CellLockManager.incrementReferenceCount(cell.getId());
httpServletRequest.setAttribute("cellId", cell.getId());

return new CellResource(ac, requestKey,
headerPersoniumEventId, headerPersoniumRuleChain, headerPersoniumVia, httpServletRequest);
return new CellResource(ac, requestKey,
headerPersoniumEventId, headerPersoniumRuleChain, headerPersoniumVia, httpServletRequest);
}

// otherwise respond error
throw PersoniumCoreException.Common.INVALID_URL_AUTHORITY
.params(accessUrlAuthority, unitAuthority + ", *." + unitAuthority);
}
}
}

10 changes: 6 additions & 4 deletions src/main/resources/personium-messages.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#
# personium.io
# Copyright 2014 FUJITSU LIMITED
# Personium
# Copyright 2014-2020 Personium Project Authors
# - FUJITSU LIMITED
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -15,7 +16,7 @@
# limitations under the License.
#

### PR ResponseMessage
### ResponseMessages
## ODATA
# PR400-OD
io.personium.core.msg.PR400-OD-0001=JSON parse error.
Expand Down Expand Up @@ -158,7 +159,7 @@ io.personium.core.msg.PR400-SM-0001=ToRelation [{0}] does not exist.
io.personium.core.msg.PR400-SM-0002=ToRelation [{0}] does not have related ExtCell.
io.personium.core.msg.PR400-SM-0003=The maximum number of destinations was exceeded.
io.personium.core.msg.PR400-SM-0004=Box corresponding to the schema can not be found from the schema-authenticated token. Schema[{0}].
# PR500-RS
# PR500-SM
io.personium.core.msg.PR500-SM-0001=Sent Message connection error.
io.personium.core.msg.PR500-SM-0002=Sent Message body parse error.

Expand Down Expand Up @@ -369,6 +370,7 @@ io.personium.core.msg.PR400-CM-0001=Required key [{0}] missing.
io.personium.core.msg.PR400-CM-0002=Field [{0}] format error. Must be [{1}].
io.personium.core.msg.PR400-CM-0003=Unknown key [{0}] specified.
io.personium.core.msg.PR400-CM-0004=JSON parse error. [{0}].
io.personium.core.msg.PR400-CM-0005=Invalid URL authority [{0}]. This unit is configured for URL authority [{1}]

io.personium.core.msg.PR409-CM-0001=Cell status is [import failed]. Only Unit Level's APIs, CellImport and GetToken are executable.
io.personium.core.msg.PR409-CM-0002=Because [{0}] is being executed, writing to cell can not be done.
Expand Down
189 changes: 189 additions & 0 deletions src/test/java/io/personium/core/rs/FacadeResourceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/**
* Personium
* Copyright 2014-2020 Personium Project
* - Akio Shimono
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.personium.core.rs;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;

import java.net.URI;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.UriInfo;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.personium.common.utils.CommonUtils;
import io.personium.core.PersoniumCoreException;
import io.personium.core.PersoniumUnitConfig;
import io.personium.core.model.ModelFactory;
import io.personium.core.rs.unit.UnitResource;

/**
* Unit test class for FacadeResource.
*
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest(ModelFactory.class)
public class FacadeResourceTest {
static Logger log = LoggerFactory.getLogger(FacadeResourceTest.class);
static volatile String shelterFqdn;

/**
* Class-level setup method.
*/
@BeforeClass
public static void beforeClass() {
shelterFqdn = CommonUtils.getFQDN();
CommonUtils.setFQDN("unit.example");
PersoniumUnitConfig.set(PersoniumUnitConfig.UNIT_PORT, "8801");
PersoniumUnitConfig.set(PersoniumUnitConfig.UNIT_SCHEME, "https");
}

/**
* Class-level tear-down method.
*/
@AfterClass
public static void afterClass() {
CommonUtils.setFQDN(shelterFqdn);
PersoniumUnitConfig.reload();
}

/**
* Test for facade() method.
*/
@Test
public void facade_PathBased_ValidUrlAccess_ShouldReturn_UnitResource() {
PersoniumUnitConfig.set(PersoniumUnitConfig.PATH_BASED_CELL_URL_ENABLED, "true");

URI[] accessUrlList = new URI[] {
URI.create("https://unit.example:8801/"),
URI.create("https://unit.example:8801/cell/_ctl"),
URI.create("https://unit.example:8801/cell/box/file.txt")
};
this.assertFacadeReturnsUnitResource(accessUrlList);
}

/**
* Test for facade() method.
*/
@Test
public void facade_SubdomainBased_ValidUrlAccess_ShouldReturn_UnitResource() {
PersoniumUnitConfig.set(PersoniumUnitConfig.PATH_BASED_CELL_URL_ENABLED, "false");
URI[] accessUrlList = new URI[] {
URI.create("https://unit.example:8801/"),
URI.create("https://unit.example:8801/_ctl"),
URI.create("https://unit.example:8801/box/file.txt")
};
this.assertFacadeReturnsUnitResource(accessUrlList);
}
private void assertFacadeReturnsUnitResource(URI[] accessUrlList) {
for (URI accessUrl : accessUrlList) {
UriInfo uriInfo = Mockito.mock(UriInfo.class);
HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class);

Mockito.doReturn(null).when(httpServletRequest).getAttribute(anyString());
Mockito.doReturn(accessUrl).when(uriInfo).getBaseUri();
PowerMockito.mockStatic(ModelFactory.class);
PowerMockito.when(ModelFactory.cellFromName(anyString())).thenReturn(null);

/* Target class of unit test. */
FacadeResource facadeResource = new FacadeResource();
Object o = facadeResource.facade(null, null, null, accessUrl.getHost(), null,
null, null, null, null, uriInfo, httpServletRequest);
assertTrue(o instanceof UnitResource);
}
}

/**
* Test for facade() method.
*/
@Test
public void facade_PathBased_InvalidUrlAccess_ShouldThrow_Exception() {
PersoniumUnitConfig.set(PersoniumUnitConfig.PATH_BASED_CELL_URL_ENABLED, "true");

URI[] accessUrlList = new URI[] {
URI.create("https://cell.unit.example:8801/"),
URI.create("https://127.0.0.1:8801/"),
URI.create("https://different.example:8801/"),
URI.create("https://unit.example/_ctl"),
URI.create("https://unit.example:8800/box/file.txt")
};
this.assertFacadeThrowsPersoniumCoreException(accessUrlList,
PersoniumCoreException.Common.INVALID_URL_AUTHORITY);
}
/**
* Test for facade() method.
*/
@Test
public void facade_SubdomainBased_InvalidUrlAccess_ShouldThrow_Exception() {
PersoniumUnitConfig.set(PersoniumUnitConfig.PATH_BASED_CELL_URL_ENABLED, "false");
URI[] accessUrlList2 = new URI[] {
URI.create("https://cell.unit.example:8801/"),
URI.create("https://cell.unit.example:8801/_ctl")
};
this.assertFacadeThrowsPersoniumCoreException(accessUrlList2, PersoniumCoreException.Dav.CELL_NOT_FOUND);

URI[] accessUrlList = new URI[] {
URI.create("https://cell.unit.example:8800/"),
URI.create("https://sub.cell.unit.example:8801/"),
URI.create("https://127.0.0.1:8801/"),
URI.create("https://different.example:8801/"),
URI.create("https://unit.example/_ctl"),
URI.create("https://unit.example:8800/box/file.txt")
};
this.assertFacadeThrowsPersoniumCoreException(accessUrlList,
PersoniumCoreException.Common.INVALID_URL_AUTHORITY);
}

private void assertFacadeThrowsPersoniumCoreException(
URI[] accessUrlList, PersoniumCoreException expectedException) {

for (URI accessUrl : accessUrlList) {
UriInfo uriInfo = Mockito.mock(UriInfo.class);
HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class);

Mockito.doReturn(null).when(httpServletRequest).getAttribute(anyString());
Mockito.doReturn(accessUrl).when(uriInfo).getBaseUri();

PowerMockito.mockStatic(ModelFactory.class);
PowerMockito.when(ModelFactory.cellFromName(anyString())).thenReturn(null);

/* Target class of unit test. */
FacadeResource facadeResource = new FacadeResource();
try {
facadeResource.facade(null, null, null, accessUrl.getHost(), null,
null, null, null, null, uriInfo, httpServletRequest);
fail("Should throw Exception for URL = " + accessUrl.toString());
} catch (PersoniumCoreException pce) {
assertEquals(expectedException.getCode(), pce.getCode());
}
}
}
}