This is an automated email from the ASF dual-hosted git repository. danhaywood pushed a commit to branch CAUSEWAY-3676 in repository https://gitbox.apache.org/repos/asf/causeway.git
commit d13e00b9433a9e862060da97bb81d2fc86a14b7c Author: danhaywood <[email protected]> AuthorDate: Mon Feb 19 17:45:01 2024 +0000 CAUSEWAY-3676: adds config properties to control whether/how resources can be downloaded Also uses spring.graphql.path as the location of the resource controller --- .../core/config/CausewayConfiguration.java | 50 ++++++++- .../viewer/graphql/model/domain/GqlvMeta.java | 45 ++++++-- .../graphql/model/domain/GqlvPropertyGetBlob.java | 16 ++- .../model/domain/GqlvPropertyGetBlobBytes.java | 6 +- .../graphql/model/domain/GqlvPropertyGetClob.java | 32 ++++-- .../model/domain/GqlvPropertyGetClobChars.java | 8 +- .../src/test/resources/application-test.properties | 4 +- viewers/graphql/test/src/test/resources/schema.gql | 75 ------------- .../viewer/controller/ResourceController.java | 121 +++++++++++++++------ 9 files changed, 222 insertions(+), 135 deletions(-) diff --git a/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java b/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java index 036f2bac03..067b072e45 100644 --- a/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java +++ b/core/config/src/main/java/org/apache/causeway/core/config/CausewayConfiguration.java @@ -2433,6 +2433,55 @@ public class CausewayConfiguration { private String zonedDateTimeFormat = "yyyy-MM-dd'T'HH:mm:ssXXX"; } + /** + * The different ways in which resources ({@link org.apache.causeway.applib.value.Blob} bytes, + * {@link org.apache.causeway.applib.value.Clob} chars, grids and icons) can be downloaded from the + * resource controller. + */ + public enum ResponseType { + /** + * Do not allow the resources to be downloaded at all. This is the default. + * + * <p> + * In this case any {@link org.apache.causeway.applib.value.Blob} and + * {@link org.apache.causeway.applib.value.Clob} properties will <i>not</i> provide a link to + * the URL. Attempting to download from the resource controller will result in a 403 (forbidden). + * </p> + */ + FORBIDDEN, + /** + * Allows resources to be downloaded directly. + * + * <p> + * <b>IMPORTANT: </b> if enabling this configuration property, make sure that the <code>ResourcesController</code> endpoints + * are secured appropriately. + * </p> + */ + DIRECT, + /** + * Allows resources to be downloaded as attachments (using <code>Content-Disposition</code> header). + * + * <p> + * <b>IMPORTANT: </b> if enabling this configuration property, make sure that the <code>ResourcesController</code> endpoints + * are secured appropriately. + * </p> + */ + ATTACHMENT, + ; + } + + @Getter + private final Resources resources = new Resources(); + @Data + public static class Resources { + /** + * How resources ({@link org.apache.causeway.applib.value.Blob} bytes, + * {@link org.apache.causeway.applib.value.Clob} chars, grids and icons) can be downloaded from the + * resource controller. + */ + private ResponseType responseType = ResponseType.FORBIDDEN; + } + @Getter private final Authentication authentication = new Authentication(); @Data @@ -2454,7 +2503,6 @@ public class CausewayConfiguration { private List<String> roles; } } - } private final Restfulobjects restfulobjects = new Restfulobjects(); diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMeta.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMeta.java index 7249f31e69..7c5de5173e 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMeta.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvMeta.java @@ -25,6 +25,7 @@ import graphql.schema.DataFetchingEnvironment; import org.apache.causeway.applib.services.bookmark.Bookmark; import org.apache.causeway.applib.services.bookmark.BookmarkService; import org.apache.causeway.applib.services.metamodel.BeanSort; +import org.apache.causeway.core.config.CausewayConfiguration; import org.apache.causeway.core.metamodel.facets.members.cssclass.CssClassFacet; import org.apache.causeway.core.metamodel.facets.object.entity.EntityFacet; import org.apache.causeway.core.metamodel.facets.object.layout.LayoutFacet; @@ -34,6 +35,8 @@ import org.apache.causeway.core.metamodel.objectmanager.ObjectManager; import org.apache.causeway.viewer.graphql.model.context.Context; import org.apache.causeway.viewer.graphql.model.mmproviders.ObjectSpecificationProvider; +import org.springframework.beans.factory.annotation.Value; + import lombok.val; public class GqlvMeta extends GqlvAbstractCustom { @@ -43,12 +46,14 @@ public class GqlvMeta extends GqlvAbstractCustom { private final GqlvMetaLogicalTypeName metaLogicalTypeName; private final GqlvMetaVersion metaVersion; private final GqlvMetaTitle metaTitle; - private final GqlvMetaIcon metaIconName; + private final GqlvMetaIcon metaIcon; private final GqlvMetaCssClass metaCssClass; private final GqlvMetaLayout metaLayout; private final GqlvMetaGrid metaGrid; private final GqlvMetaSaveAs metaSaveAs; + private final CausewayConfiguration.Viewer.Graphql graphqlConfiguration; + public GqlvMeta( final Holder holder, final Context context @@ -56,12 +61,14 @@ public class GqlvMeta extends GqlvAbstractCustom { super(TypeNames.metaTypeNameFor(holder.getObjectSpecification()), context); this.holder = holder; + this.graphqlConfiguration = context.causewayConfiguration.getViewer().getGraphql(); + if(isBuilt()) { this.metaId = null; this.metaLogicalTypeName = null; this.metaVersion = null; this.metaTitle = null; - this.metaIconName = null; + this.metaIcon = null; this.metaCssClass = null; this.metaLayout = null; this.metaGrid = null; @@ -73,16 +80,21 @@ public class GqlvMeta extends GqlvAbstractCustom { addChildFieldFor(this.metaLogicalTypeName = new GqlvMetaLogicalTypeName(context)); addChildFieldFor(this.metaVersion = isEntity() ? new GqlvMetaVersion(context) : null); addChildFieldFor(this.metaTitle = new GqlvMetaTitle(context)); - addChildFieldFor(this.metaIconName = new GqlvMetaIcon(context)); addChildFieldFor(this.metaCssClass = new GqlvMetaCssClass(context)); addChildFieldFor(this.metaLayout = new GqlvMetaLayout(context)); - addChildFieldFor(this.metaGrid = new GqlvMetaGrid(context)); addChildFieldFor(this.metaSaveAs = new GqlvMetaSaveAs(context)); - val fieldName = context.causewayConfiguration.getViewer().getGraphql().getMetaData().getFieldName(); + addChildFieldFor(this.metaIcon = isResourceNotForbidden() ? new GqlvMetaIcon(context) : null); + addChildFieldFor(this.metaGrid = isResourceNotForbidden() ? new GqlvMetaGrid(context) : null); + + val fieldName = graphqlConfiguration.getMetaData().getFieldName(); buildObjectTypeAndField(fieldName); } + private boolean isResourceNotForbidden() { + return graphqlConfiguration.getResources().getResponseType() != CausewayConfiguration.Viewer.Graphql.ResponseType.FORBIDDEN; + } + private boolean isEntity() { return holder.getObjectSpecification().getBeanSort() == BeanSort.ENTITY; } @@ -90,6 +102,7 @@ public class GqlvMeta extends GqlvAbstractCustom { @Override protected void addDataFetchersForChildren() { if (metaId == null) { + // none of the fields will have been initialized return; } metaId.addDataFetcher(this); @@ -98,17 +111,21 @@ public class GqlvMeta extends GqlvAbstractCustom { metaVersion.addDataFetcher(this); } metaTitle.addDataFetcher(this); - metaIconName.addDataFetcher(this); metaCssClass.addDataFetcher(this); metaLayout.addDataFetcher(this); - metaGrid.addDataFetcher(this); metaSaveAs.addDataFetcher(this); + if (metaGrid != null) { + metaGrid.addDataFetcher(this); + } + if (metaIcon != null) { + metaIcon.addDataFetcher(this); + } } @Override public Object fetchData(final DataFetchingEnvironment environment) { return context.bookmarkService.bookmarkFor(environment.getSource()) - .map(bookmark -> new Fetcher(bookmark, context.bookmarkService, context.objectManager)) + .map(bookmark -> new Fetcher(bookmark, context.bookmarkService, context.objectManager, context.causewayConfiguration)) .orElseThrow(); } @@ -121,14 +138,20 @@ public class GqlvMeta extends GqlvAbstractCustom { private final Bookmark bookmark; private final BookmarkService bookmarkService; private final ObjectManager objectManager; + private final CausewayConfiguration causewayConfiguration; + private final String graphqlPath; Fetcher( final Bookmark bookmark, final BookmarkService bookmarkService, - final ObjectManager objectManager) { + final ObjectManager objectManager, + final CausewayConfiguration causewayConfiguration + ) { this.bookmark = bookmark; this.bookmarkService = bookmarkService; this.objectManager = objectManager; + this.causewayConfiguration = causewayConfiguration; + this.graphqlPath = causewayConfiguration.valueOf("spring.graphql.path").orElse("/graphql"); } public String logicalTypeName(){ @@ -193,8 +216,8 @@ public class GqlvMeta extends GqlvAbstractCustom { return managedObject() .flatMap(Bookmarkable::getBookmark ).map(bookmark -> String.format( - "///%s/object/%s:%s/_meta/%s", - "graphql", bookmark.getLogicalTypeName(), bookmark.getIdentifier(), resource) ) + "//%s/object/%s:%s/%s/%s", + graphqlPath, bookmark.getLogicalTypeName(), bookmark.getIdentifier(), causewayConfiguration.getViewer().getGraphql().getMetaData().getFieldName(), resource) ) .orElse(null); } diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlob.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlob.java index 6380205c24..794bcca368 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlob.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlob.java @@ -22,6 +22,7 @@ import graphql.schema.DataFetchingEnvironment; import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; +import org.apache.causeway.core.config.CausewayConfiguration; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation; import org.apache.causeway.viewer.graphql.model.context.Context; @@ -39,13 +40,16 @@ public class GqlvPropertyGetBlob final GqlvPropertyGetBlobMimeType blobMimeType; final GqlvPropertyGetBlobName blobBytes; + private final CausewayConfiguration.Viewer.Graphql graphqlConfiguration; + public GqlvPropertyGetBlob( final Holder holder, final Context context) { super(TypeNames.propertyBlobTypeNameFor(holder.getObjectSpecification(), holder.getObjectMember()), context); - this.holder = holder; + this.graphqlConfiguration = context.causewayConfiguration.getViewer().getGraphql(); + if (isBuilt()) { // type already exists, nothing else to do. this.blobName = null; @@ -56,7 +60,7 @@ public class GqlvPropertyGetBlob addChildFieldFor(blobName = new GqlvPropertyGetBlobBytes(this, context)); addChildFieldFor(blobMimeType = new GqlvPropertyGetBlobMimeType(this, context)); - addChildFieldFor(blobBytes = new GqlvPropertyGetBlobName(this, context)); + addChildFieldFor(blobBytes = isResourceNotForbidden() ? new GqlvPropertyGetBlobName(this, context) : null); setField(newFieldDefinition() .name("get") @@ -64,6 +68,10 @@ public class GqlvPropertyGetBlob .build()); } + private boolean isResourceNotForbidden() { + return graphqlConfiguration.getResources().getResponseType() != CausewayConfiguration.Viewer.Graphql.ResponseType.FORBIDDEN; + } + @Override protected Object fetchData(final DataFetchingEnvironment dataFetchingEnvironment) { return BookmarkedPojo.sourceFrom(dataFetchingEnvironment, context); @@ -76,7 +84,9 @@ public class GqlvPropertyGetBlob } blobName.addDataFetcher(this); blobMimeType.addDataFetcher(this); - blobBytes.addDataFetcher(this); + if (blobBytes != null) { + blobBytes.addDataFetcher(this); + } } @Override diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlobBytes.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlobBytes.java index 2c1fe83471..5c6fee5d46 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlobBytes.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetBlobBytes.java @@ -30,10 +30,14 @@ import lombok.val; public class GqlvPropertyGetBlobBytes extends GqlvPropertyGetBlobAbstract { + private final String graphqlPath; + public GqlvPropertyGetBlobBytes( final Holder holder, final Context context) { super(holder, context, "bytes"); + + this.graphqlPath = context.causewayConfiguration.valueOf("spring.graphql.path").orElse("/graphql"); } @Override @@ -42,7 +46,7 @@ public class GqlvPropertyGetBlobBytes extends GqlvPropertyGetBlobAbstract { Optional<Bookmark> bookmarkIfAny = context.bookmarkService.bookmarkFor(sourcePojo); return bookmarkIfAny.map(x -> String.format( - "///%s/object/%s:%s/%s/blobBytes", "graphql", x.getLogicalTypeName(), x.getIdentifier(), holder.getObjectAssociation().getId())).orElse(null); + "//%s/object/%s:%s/%s/blobBytes", graphqlPath, x.getLogicalTypeName(), x.getIdentifier(), holder.getObjectAssociation().getId())).orElse(null); } diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClob.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClob.java index 6983f85fd7..75e756dfec 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClob.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClob.java @@ -22,6 +22,7 @@ import graphql.schema.DataFetchingEnvironment; import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition; +import org.apache.causeway.core.config.CausewayConfiguration; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation; import org.apache.causeway.viewer.graphql.model.context.Context; @@ -35,9 +36,11 @@ public class GqlvPropertyGetClob { final Holder holder; - final GqlvPropertyGetClobChars clobChars; - final GqlvPropertyGetClobMimeType clobMimeType; final GqlvPropertyGetClobName clobName; + final GqlvPropertyGetClobMimeType clobMimeType; + final GqlvPropertyGetClobChars clobChars; + + private final CausewayConfiguration.Viewer.Graphql graphqlConfiguration; public GqlvPropertyGetClob( final Holder holder, @@ -45,16 +48,19 @@ public class GqlvPropertyGetClob super(TypeNames.propertyBlobTypeNameFor(holder.getObjectSpecification(), holder.getObjectMember()), context); this.holder = holder; - if(isBuilt()) { - this.clobChars = null; - this.clobMimeType = null; + this.graphqlConfiguration = context.causewayConfiguration.getViewer().getGraphql(); + + if (isBuilt()) { + // type already exists, nothing else to do. this.clobName = null; + this.clobMimeType = null; + this.clobChars = null; return; } - addChildFieldFor(clobChars = new GqlvPropertyGetClobChars(this, context)); - addChildFieldFor(clobMimeType = new GqlvPropertyGetClobMimeType(this, context)); addChildFieldFor(clobName = new GqlvPropertyGetClobName(this, context)); + addChildFieldFor(clobMimeType = new GqlvPropertyGetClobMimeType(this, context)); + addChildFieldFor(clobChars = isResourceNotForbidden() ? new GqlvPropertyGetClobChars(this, context) : null); setField(newFieldDefinition() .name("get") @@ -62,6 +68,10 @@ public class GqlvPropertyGetClob .build()); } + private boolean isResourceNotForbidden() { + return graphqlConfiguration.getResources().getResponseType() != CausewayConfiguration.Viewer.Graphql.ResponseType.FORBIDDEN; + } + @Override protected Object fetchData(final DataFetchingEnvironment dataFetchingEnvironment) { return BookmarkedPojo.sourceFrom(dataFetchingEnvironment, context); @@ -69,12 +79,14 @@ public class GqlvPropertyGetClob @Override protected void addDataFetchersForChildren() { - if(clobChars == null) { + if(clobName == null) { return; } - clobChars.addDataFetcher(this); - clobMimeType.addDataFetcher(this); clobName.addDataFetcher(this); + clobMimeType.addDataFetcher(this); + if(clobChars != null) { + clobChars.addDataFetcher(this); + } } @Override diff --git a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClobChars.java b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClobChars.java index fd59b02d32..6acff3198d 100644 --- a/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClobChars.java +++ b/viewers/graphql/model/src/main/java/org/apache/causeway/viewer/graphql/model/domain/GqlvPropertyGetClobChars.java @@ -30,10 +30,14 @@ import lombok.val; public class GqlvPropertyGetClobChars extends GqlvPropertyGetClobAbstract { + private final String graphqlPath; + public GqlvPropertyGetClobChars( final Holder holder, final Context context) { - super(holder, context, "bytes"); + super(holder, context, "chars"); + + this.graphqlPath = context.causewayConfiguration.valueOf("spring.graphql.path").orElse("/graphql"); } @Override @@ -42,7 +46,7 @@ public class GqlvPropertyGetClobChars extends GqlvPropertyGetClobAbstract { Optional<Bookmark> bookmarkIfAny = context.bookmarkService.bookmarkFor(sourcePojo); return bookmarkIfAny.map(x -> String.format( - "///%s/object/%s:%s/%s/clobChars", "graphql", x.getLogicalTypeName(), x.getIdentifier(), holder.getObjectAssociation().getId())).orElse(null); + "//%s/object/%s:%s/%s/clobChars", graphqlPath, x.getLogicalTypeName(), x.getIdentifier(), holder.getObjectAssociation().getId())).orElse(null); } diff --git a/viewers/graphql/test/src/test/resources/application-test.properties b/viewers/graphql/test/src/test/resources/application-test.properties index cbfd204edd..514fd18e07 100644 --- a/viewers/graphql/test/src/test/resources/application-test.properties +++ b/viewers/graphql/test/src/test/resources/application-test.properties @@ -26,4 +26,6 @@ decorator.datasource.datasource-proxy.format-sql=false decorator.datasource.datasource-proxy.json-format=false # Enable Query Metrics -decorator.datasource.datasource-proxy.count-query=false \ No newline at end of file +decorator.datasource.datasource-proxy.count-query=false + +causeway.viewer.graphql.resources.responseType=ATTACHMENT \ No newline at end of file diff --git a/viewers/graphql/test/src/test/resources/schema.gql b/viewers/graphql/test/src/test/resources/schema.gql index 2e857f1308..99035dcb7a 100644 --- a/viewers/graphql/test/src/test/resources/schema.gql +++ b/viewers/graphql/test/src/test/resources/schema.gql @@ -218,8 +218,6 @@ type causeway_applib_DomainObjectList__elementTypeFqcn__gqlv_property { type causeway_applib_DomainObjectList__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -266,8 +264,6 @@ type causeway_applib_FacetGroupNode__facets__gqlv_property { type causeway_applib_FacetGroupNode__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -298,8 +294,6 @@ type causeway_applib_ParameterNode__childNodes__gqlv_collection { type causeway_applib_ParameterNode__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -341,8 +335,6 @@ type causeway_applib_PropertyNode__childNodes__gqlv_collection { type causeway_applib_PropertyNode__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -394,8 +386,6 @@ type causeway_applib_RoleMemento__description__gqlv_property { type causeway_applib_RoleMemento__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -436,8 +426,6 @@ type causeway_applib_TypeNode__domainClassDto__gqlv_property { type causeway_applib_TypeNode__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -498,8 +486,6 @@ type causeway_applib_UserMemento__avatarUrl__gqlv_property { type causeway_applib_UserMemento__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -614,8 +600,6 @@ type causeway_applib_node_ActionNode__childNodes__gqlv_collection { type causeway_applib_node_ActionNode__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -666,8 +650,6 @@ type causeway_applib_node_CollectionNode__collection__gqlv_property { type causeway_applib_node_CollectionNode__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -717,8 +699,6 @@ type causeway_applib_node_FacetAttrNode__facetAttr__gqlv_property { type causeway_applib_node_FacetAttrNode__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -760,8 +740,6 @@ type causeway_applib_node_FacetNode__facet__gqlv_property { type causeway_applib_node_FacetNode__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -805,8 +783,6 @@ type causeway_conf_ConfigurationProperty { type causeway_conf_ConfigurationProperty__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -848,8 +824,6 @@ type causeway_conf_ConfigurationViewmodel__environment__gqlv_collection { type causeway_conf_ConfigurationViewmodel__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -881,8 +855,6 @@ type causeway_feat_ApplicationFeatureViewModel { type causeway_feat_ApplicationFeatureViewModel__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -942,8 +914,6 @@ type causeway_feat_ApplicationNamespace__contents__gqlv_collection { type causeway_feat_ApplicationNamespace__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -1019,8 +989,6 @@ type causeway_feat_ApplicationTypeAction__actionSemantics__gqlv_property { type causeway_feat_ApplicationTypeAction__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -1102,8 +1070,6 @@ type causeway_feat_ApplicationTypeCollection__elementType__gqlv_property { type causeway_feat_ApplicationTypeCollection__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -1156,8 +1122,6 @@ type causeway_feat_ApplicationTypeMember { type causeway_feat_ApplicationTypeMember__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -1223,8 +1187,6 @@ type causeway_feat_ApplicationTypeProperty__derived__gqlv_property { type causeway_feat_ApplicationTypeProperty__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -1310,8 +1272,6 @@ type causeway_feat_ApplicationType__collections__gqlv_collection { type causeway_feat_ApplicationType__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -1412,8 +1372,6 @@ type causeway_schema_metamodel_v2_DomainClassDto__facets__gqlv_property { type causeway_schema_metamodel_v2_DomainClassDto__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -1472,8 +1430,6 @@ type causeway_security_LoginRedirect { type causeway_security_LoginRedirect__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -1520,8 +1476,6 @@ type causeway_testing_fixtures_FixtureResult__fixtureScriptClassName__gqlv_prope type causeway_testing_fixtures_FixtureResult__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -1552,8 +1506,6 @@ type java_lang_Runnable { type java_lang_Runnable__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -1567,8 +1519,6 @@ type java_util_Map { type java_util_Map__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -1582,8 +1532,6 @@ type java_util_SortedMap { type java_util_SortedMap__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -1597,8 +1545,6 @@ type java_util_concurrent_Callable { type java_util_concurrent_Callable__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -1612,8 +1558,6 @@ type java_util_function_BiFunction { type java_util_function_BiFunction__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -1627,8 +1571,6 @@ type java_util_function_Consumer { type java_util_function_Consumer__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -1642,8 +1584,6 @@ type java_util_function_Function { type java_util_function_Function__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -1657,8 +1597,6 @@ type java_util_stream_Stream { type java_util_stream_Stream__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -1680,8 +1618,6 @@ type org_apache_causeway_core_metamodel_inspect_model_MMNode__childNodes__gqlv_c type org_apache_causeway_core_metamodel_inspect_model_MMNode__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -1712,8 +1648,6 @@ type org_apache_causeway_core_metamodel_inspect_model_MemberNode__childNodes__gq type org_apache_causeway_core_metamodel_inspect_model_MemberNode__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -1756,8 +1690,6 @@ type org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript__f type org_apache_causeway_testing_fixtures_applib_fixturescripts_FixtureScript__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -2752,8 +2684,6 @@ type university_dept_Department__deptHead__gqlv_property { type university_dept_Department__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -2927,8 +2857,6 @@ type university_dept_DeptHead__department__gqlv_property { type university_dept_DeptHead__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -3007,8 +2935,6 @@ type university_dept_StaffMember__department__gqlv_property { type university_dept_StaffMember__gqlv_meta { cssClass: String - grid: String - icon: String id: String! layout: String logicalTypeName: String! @@ -3048,7 +2974,6 @@ type university_dept_StaffMember__photo__gqlv_property { type university_dept_StaffMember__photo__gqlv_property_blob { bytes: String mimeType: String - name: String } type university_dept_Staff__createStaffMember__department__gqlv_action_parameter { diff --git a/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/controller/ResourceController.java b/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/controller/ResourceController.java index 8e792e94b2..4ea4ff98c6 100644 --- a/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/controller/ResourceController.java +++ b/viewers/graphql/viewer/src/main/java/org/apache/causeway/viewer/graphql/viewer/controller/ResourceController.java @@ -7,12 +7,14 @@ import javax.inject.Inject; import org.apache.causeway.applib.layout.grid.Grid; import org.apache.causeway.commons.io.JaxbUtils; +import org.apache.causeway.core.config.CausewayConfiguration; import org.apache.causeway.core.metamodel.facets.object.grid.GridFacet; import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIcon; import org.springframework.http.ContentDisposition; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.lang.Nullable; @@ -30,17 +32,26 @@ import org.apache.causeway.core.metamodel.object.ManagedObject; import org.apache.causeway.core.metamodel.objectmanager.ObjectManager; import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation; -import lombok.RequiredArgsConstructor; import lombok.Value; import lombok.val; @RestController() @RequestMapping("/graphql/object") -@RequiredArgsConstructor(onConstructor_ = {@Inject}) public class ResourceController { private final BookmarkService bookmarkService; private final ObjectManager objectManager; + private final CausewayConfiguration.Viewer.Graphql graphqlConfiguration; + + @Inject + public ResourceController( + final BookmarkService bookmarkService, + final ObjectManager objectManager, + final CausewayConfiguration causewayConfiguration) { + this.bookmarkService = bookmarkService; + this.objectManager = objectManager; + this.graphqlConfiguration = causewayConfiguration.getViewer().getGraphql(); + } @GetMapping(value = "/{logicalTypeName}:{id}/{propertyId}/blobBytes") public ResponseEntity<byte[]> propertyBlobBytes( @@ -48,14 +59,26 @@ public class ResourceController { @PathVariable final String id, @PathVariable final String propertyId ) { + val responseType = graphqlConfiguration.getResources().getResponseType(); + + // TODO: perhaps a filter would factor this check out? + if (responseType == CausewayConfiguration.Viewer.Graphql.ResponseType.FORBIDDEN) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + return valueOfProperty(logicalTypeName, id, propertyId) .filter(Blob.class::isInstance) .map(Blob.class::cast) - .map(blob -> ResponseEntity.ok() - .contentType(MediaType.asMediaType(MimeType.valueOf(blob.getMimeType().toString()))) - .header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment().filename(blob.getName()).build().toString()) - .contentLength(blob.getBytes().length) - .body(blob.getBytes())) + .map(blob -> { + val bodyBuilder = ResponseEntity.ok() + .contentType(MediaType.asMediaType(MimeType.valueOf(blob.getMimeType().toString()))); + if (responseType == CausewayConfiguration.Viewer.Graphql.ResponseType.ATTACHMENT) { + bodyBuilder + .header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment().filename(blob.getName()).build().toString()) + .contentLength(blob.getBytes().length); + } + return bodyBuilder.body(blob.getBytes()); + }) .orElse(ResponseEntity.notFound().build()); } @@ -65,50 +88,91 @@ public class ResourceController { @PathVariable final String id, @PathVariable final String propertyId ) { + val responseType = graphqlConfiguration.getResources().getResponseType(); + + // TODO: perhaps a filter would factor this check out? + if (responseType == CausewayConfiguration.Viewer.Graphql.ResponseType.FORBIDDEN) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + return valueOfProperty(logicalTypeName, id, propertyId) .filter(Clob.class::isInstance) .map(Clob.class::cast) - .map(clob -> ResponseEntity.ok() - .contentType(MediaType.asMediaType(MimeType.valueOf(clob.getMimeType().toString()))) - .header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment().filename(clob.getName()).build().toString()) - .contentLength(clob.getChars().length()) - .body(clob.getChars())) + .map(clob -> { + val bodyBuilder = ResponseEntity.ok() + .contentType(MediaType.asMediaType(MimeType.valueOf(clob.getMimeType().toString()))); + if (responseType == CausewayConfiguration.Viewer.Graphql.ResponseType.ATTACHMENT) { + bodyBuilder.header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment().filename(clob.getName()).build().toString()) + .contentLength(clob.getChars().length()); + } + return bodyBuilder.body(clob.getChars()); + }) .orElse(ResponseEntity.notFound().build()); } - @GetMapping(value = "/{logicalTypeName}:{id}/_meta/grid") + @GetMapping(value = "/{logicalTypeName}:{id}/{_meta}/grid") public ResponseEntity<String> grid( @PathVariable final String logicalTypeName, - @PathVariable final String id + @PathVariable final String id, + @PathVariable final String _meta ) { + val responseType = graphqlConfiguration.getResources().getResponseType(); + + // TODO: perhaps a filter would factor this check out? + if (responseType == CausewayConfiguration.Viewer.Graphql.ResponseType.FORBIDDEN) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + + if (!_meta.equals(graphqlConfiguration.getMetaData().getFieldName())) { + return ResponseEntity.notFound().build(); + } + return lookup(logicalTypeName, id) .map(ResourceController::gridOf) .filter(Objects::nonNull) .map(JaxbUtils::toStringUtf8) .map(x -> x.replaceAll("(\r\n)", "\n")) - .map(gridText -> ResponseEntity.ok() - .contentType(MediaType.APPLICATION_XML) - .header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment().filename(logicalTypeName + ".layout.xml").build().toString()) - .contentLength(gridText.length()) - .body(gridText)) + .map(gridText -> { + val bodyBuilder = ResponseEntity.ok() + .contentType(MediaType.APPLICATION_XML); + if (responseType == CausewayConfiguration.Viewer.Graphql.ResponseType.ATTACHMENT) { + bodyBuilder + .header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment().filename(logicalTypeName + ".layout.xml").build().toString()) + .contentLength(gridText.length()); + } + return bodyBuilder.body(gridText); + }) .orElse(ResponseEntity.notFound().build()); } - @GetMapping(value = "/{logicalTypeName}:{id}/_meta/icon") + @GetMapping(value = "/{logicalTypeName}:{id}/{_meta}/icon") public ResponseEntity<byte[]> icon( @PathVariable final String logicalTypeName, - @PathVariable final String id + @PathVariable final String id, + @PathVariable final String _meta ) { + // TODO: perhaps a filter would factor this check out? + val responseType = graphqlConfiguration.getResources().getResponseType(); + if (responseType == CausewayConfiguration.Viewer.Graphql.ResponseType.FORBIDDEN) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + if (!_meta.equals(graphqlConfiguration.getMetaData().getFieldName())) { + return ResponseEntity.notFound().build(); + } + return lookup(logicalTypeName, id) .map(ManagedObject::getIcon) .filter(Objects::nonNull) .map(objectIcon -> { val bytes = objectIcon.asBytes(); - return ResponseEntity.ok() - .contentType(MediaType.parseMediaType(objectIcon.getMimeType().getMimeType().toString())) - .header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment().filename(logicalTypeName + ".png").build().toString()) - .contentLength(bytes.length) - .body(bytes); + val bodyBuilder = ResponseEntity.ok() + .contentType(MediaType.parseMediaType(objectIcon.getMimeType().getMimeType().toString())); + if (responseType == CausewayConfiguration.Viewer.Graphql.ResponseType.ATTACHMENT) { + bodyBuilder + .header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment().filename(logicalTypeName + ".png").build().toString()) + .contentLength(bytes.length); + } + return bodyBuilder.body(bytes); }) .orElse(ResponseEntity.notFound().build()); } @@ -119,11 +183,6 @@ public class ResourceController { return facet != null ? facet.getGrid(managedObject) : null; } - @Nullable - private static ObjectIcon iconOf(ManagedObject managedObject) { - return managedObject.getIcon(); - } - private Optional<Object> valueOfProperty(String logicalTypeName, String id, String propertyId) { return lookup(logicalTypeName, id) .map(managedObject -> ManagedObjectAndPropertyIfAny.of(managedObject, managedObject.getSpecification().getProperty(propertyId)))
