Skip to content

Commit

Permalink
Merge branch 'develop' into chore/types-redux
Browse files Browse the repository at this point in the history
  • Loading branch information
edalex-ian committed Jul 15, 2021
2 parents d431c68 + 0ad6015 commit 007e2e1
Show file tree
Hide file tree
Showing 48 changed files with 46,525 additions and 29,467 deletions.
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
14.17.3
16.5.0
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
val springVersion = "5.3.8"
val springVersion = "5.3.9"

libraryDependencies ++= Seq(
"com.github.equella.jpf" % "jpf" % "1.0.7",
Expand All @@ -18,7 +18,7 @@ excludeDependencies ++= Seq(
// Spring 5 added a default logging bridge. In oEQ, this results in
// a [deduplicate: different file contents found in the following] error
// ...org.slf4j/jcl-over-slf4j/jars/jcl-over-slf4j-1.7.30.jar:org/apache/commons/logging/Log.class
// ...org.springframework/spring-jcl/jars/spring-jcl-5.3.8.jar:org/apache/commons/logging/Log.class
// ...org.springframework/spring-jcl/jars/spring-jcl-5.3.9.jar:org/apache/commons/logging/Log.class
// As per https://github.com/spring-projects/spring-framework/issues/20611 ,
// since we already have logging in place, we can safely exclude the dep from spring.
"org.springframework" % "spring-jcl"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
val springVersion = "5.3.8"
val springVersion = "5.3.9"

libraryDependencies ++= Seq(
"com.google.guava" % "guava" % "18.0",
Expand Down
2 changes: 1 addition & 1 deletion Source/Plugins/Core/com.equella.core/jarsrc/build.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
val springVersion = "5.3.8"
val springVersion = "5.3.9"

libraryDependencies ++= Seq(
"net.java.dev.jna" % "platform" % "3.5.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,8 @@ drm.rightsprovided.custom.usagetitle=Permissions for using
drm.rightsprovided.title=What rights should the end user be provided?
drm.xthisitem=this item
drmfilter.drmprotected=Protected by DRM access controls. You must authenticate yourself.
drmfilter.drmnotallowedtoaccept=Auto-login accounts who have transient DRM acceptance or Guest accounts are not allowed to accept DRM terms.
drmfilter.drmtermsaccepted=You already accepted DRM terms.
drmfilter.protectedimage=Attempted to view protected resource
dropdown.fontfamily=Font Family
dropdown.fontsize=Font Size
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,23 @@ case class ApiErrorResponse(errors: Seq[ApiResponseMessage])
object ApiErrorResponse {

def resourceNotFound(errors: String*): Response = {
Response.status(Status.NOT_FOUND).entity(responseBody(errors)).build()
buildErrorResponse(Status.NOT_FOUND, errors)
}

def badRequest(errors: String*): Response = {
Response.status(Status.BAD_REQUEST).entity(responseBody(errors)).build()
buildErrorResponse(Status.BAD_REQUEST, errors)
}

def unauthorizedRequest(errors: String*): Response = {
buildErrorResponse(Status.UNAUTHORIZED, errors)
}

def forbiddenRequest(errors: String*): Response = {
buildErrorResponse(Status.FORBIDDEN, errors)
}

private def buildErrorResponse(status: Status, errors: Seq[String]): Response = {
Response.status(status).entity(responseBody(errors)).build()
}

private def responseBody(errors: Seq[String]) = ApiErrorResponse(errors.map(ApiResponseMessage))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* The Apereo Foundation licenses this file to you 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 com.tle.web.api.drm

import com.dytech.edge.exceptions.{DRMException, ItemNotFoundException}
import com.tle.beans.item.{DrmSettings, Item, ItemId}
import com.tle.exceptions.AccessDeniedException
import com.tle.legacy.LegacyGuice
import com.tle.web.api.ApiErrorResponse.{
badRequest,
forbiddenRequest,
resourceNotFound,
unauthorizedRequest
}
import io.swagger.annotations.{Api, ApiOperation, ApiParam}
import org.jboss.resteasy.annotations.cache.NoCache
import javax.ws.rs.core.Response
import javax.ws.rs.{BadRequestException, GET, NotFoundException, POST, Path, PathParam, Produces}
import scala.util.control.Exception.allCatch
import scala.util.{Failure, Success, Try}

@NoCache
@Path("item/{uuid}/{version}/drm")
@Produces(Array("application/json"))
@Api("Item DRM")
class DrmResource {
val drmService = LegacyGuice.drmService

@GET
@ApiOperation(
value = "List DRM terms",
notes = "This endpoint is used to list an Item's DRM terms.",
response = classOf[ItemDrmDetails],
)
def getDrmTerms(@ApiParam("Item UUID") @PathParam("uuid") uuid: String,
@ApiParam("Item Version") @PathParam("version") version: Int): Response = {
val getTerms = (uuid: String, version: Int) =>
Try {
getItem.andThen(_.getDrmSettings)(new ItemId(uuid, version))
}

val mapToItemDrmDetails = (drm: DrmSettings) =>
Try {
Option(drm) match {
case Some(drmSettings) => ItemDrmDetails(drmSettings)
case None =>
throw new NotFoundException(s"Failed to find DRM terms for item: $uuid/$version")
}
}

val result = getTerms(uuid, version) flatMap mapToItemDrmDetails
respond(result)
}

@POST
@ApiOperation(
value = "Accept DRM terms",
notes = "This endpoint is used to accept an Item's DRM terms.",
response = classOf[Long],
)
def acceptDrm(@ApiParam("Item UUID") @PathParam("uuid") uuid: String,
@ApiParam("Item Version") @PathParam("version") version: Int): Response = {

val acceptLicense: Item => Long = drmService.acceptLicenseOrThrow
val result = allCatch withTry (getItem andThen acceptLicense)(new ItemId(uuid, version))
respond(result)
}

// Take a subtype of Throwable and return a function which takes a sequence of string and returns a Response.
private def mapException[T <: Throwable](e: T): Seq[String] => Response = {
e match {
case _: BadRequestException => badRequest
case _: AccessDeniedException => forbiddenRequest
case _: DRMException => unauthorizedRequest
case _ @(_: ItemNotFoundException | _: NotFoundException) => resourceNotFound
}
}

private def respond[T](attempt: Try[T]): Response =
attempt match {
case Success(result) => Response.ok().entity(result).build()
case Failure(e) => mapException(e)(Seq(e.getMessage))
}

private val getItem: ItemId => Item = LegacyGuice.itemService.getUnsecure
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* The Apereo Foundation licenses this file to you 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 com.tle.web.api.drm

import com.tle.beans.item.DrmSettings
import com.tle.core.i18n.CoreStrings
import com.tle.web.viewitem.I18nDRM
import scala.collection.JavaConverters._

case class DrmParties(
/** Server side language string for DRM party. */
title: String,
/** A list of text consisting each party's name and email. */
partyList: List[String])

case class DrmCustomTerms(
/** Server side language string for DRM terms. */
title: String,
/** Terms of using the Item. */
terms: String)

case class DrmAgreements(
/** Text describing what regular permissions are granted to the user. */
regularPermission: Option[String],
/** Text describing what additional permissions are granted to the user. */
additionalPermission: Option[String],
/** Text describing that the use of Item is limited to education sector. */
educationSector: Option[String],
/** Text describing parties related to the Item. */
parties: Option[DrmParties],
/** Other terms and conditions applied to the Item. */
customTerms: Option[DrmCustomTerms]
)

case class ItemDrmDetails(
/** Server side language string used as the DRM acceptance title */
title: String = CoreStrings.text("summary.content.termsofuse.title"),
/** Server side language string used as the DRM acceptance subtitle */
subtitle: String = CoreStrings.text("summary.content.termsofuse.terms.title"),
/** Server side language string used as the DRM acceptance description */
description: String = CoreStrings.text("summary.content.termsofuse.terms.description"),
/** All terms and conditions that user must accept to use the Item */
agreements: DrmAgreements)

object ItemDrmDetails {
def buildPermissionText(permissions: String, title: String): Option[String] = {
if (permissions.nonEmpty) Option(s"$title $permissions") else None
}

def apply(drmSettings: DrmSettings): ItemDrmDetails = {
val drmI18n = new I18nDRM(drmSettings)

val customTerms =
Option(drmI18n.getTerms).map(terms =>
DrmCustomTerms(CoreStrings.text("drm.mustagree"), terms))

val regularPermission =
buildPermissionText(drmI18n.getPermissions1List, drmI18n.getItemMayFreelyBeText)
val additionalPermission =
buildPermissionText(drmI18n.getPermissions2List, drmI18n.getAdditionallyUserMayText)

val educationSector =
if (drmI18n.isUseEducation) Option(drmI18n.getEducationSectorText) else None

val parties = if (drmI18n.isAttribution && !drmI18n.getParties.isEmpty) {
Option(
DrmParties(drmI18n.getAttributeOwnersText,
drmI18n.getParties.asScala.map(p => s"${p.getName} ${p.getEmail}").toList))
} else {
None
}

val agreements = DrmAgreements(
regularPermission,
additionalPermission,
educationSector,
parties,
customTerms
)

ItemDrmDetails(agreements = agreements)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

package com.tle.web.api.search

import com.dytech.edge.exceptions.BadRequestException
import com.dytech.edge.exceptions.{BadRequestException, DRMException}
import com.tle.beans.entity.DynaCollection
import com.tle.beans.item.attachments.{Attachment, CustomAttachment, FileAttachment}
import com.tle.beans.item.{Comment, ItemId, ItemIdKey}
Expand All @@ -27,8 +27,8 @@ import com.tle.common.beans.exception.NotFoundException
import com.tle.common.collection.AttachmentConfigConstants
import com.tle.common.search.DefaultSearch
import com.tle.common.search.whereparser.WhereParser
import com.tle.common.usermanagement.user.CurrentUser
import com.tle.core.freetext.queries.FreeTextBooleanQuery

import com.tle.core.item.security.ItemSecurityConstants
import com.tle.core.item.serializer.{ItemSerializerItemBean, ItemSerializerService}
import com.tle.core.security.ACLChecks.hasAcl
Expand All @@ -40,10 +40,14 @@ import com.tle.web.api.item.equella.interfaces.beans.{
FileAttachmentBean
}
import com.tle.web.api.item.interfaces.beans.AttachmentBean
import com.tle.web.api.search.model.{SearchParam, SearchResultAttachment, SearchResultItem}
import com.tle.web.api.search.model.{
SearchParam,
SearchResultAttachment,
SearchResultItem,
DrmStatus
}
import com.tle.web.controls.resource.ResourceAttachmentBean
import com.tle.web.controls.youtube.YoutubeAttachmentBean

import java.time.format.DateTimeParseException
import java.time.{LocalDate, LocalDateTime, LocalTime, ZoneId}
import java.util.Date
Expand Down Expand Up @@ -262,6 +266,7 @@ object SearchHelper {
links = getLinksFromBean(bean),
bookmarkId = getBookmarkId(key),
isLatestVersion = isLatestVersion(key),
drmStatus = getItemDrmStatus(item.idKey)
)
}

Expand Down Expand Up @@ -297,6 +302,27 @@ object SearchHelper {
.toList)
}

def getItemDrmStatus(itemKey: ItemIdKey): Option[DrmStatus] = {
for {
item <- Option(LegacyGuice.itemService.getUnsecureIfExists(itemKey))
_ <- Option(item.getDrmSettings)
termsAccepted = try {
LegacyGuice.drmService.hasAcceptedOrRequiresNoAcceptance(item, false, false)
} catch {
// This exception is only thrown when the DRM has maximum number of acceptance allowable times.
case _: DRMException => false
}
isAuthorised = try {
LegacyGuice.drmService.isAuthorised(item, CurrentUser.getUserState.getIpAddress)
true
} catch {
case _: DRMException => false
}
} yield {
DrmStatus(termsAccepted, isAuthorised)
}
}

def getItemComments(key: ItemIdKey): Option[java.util.List[Comment]] =
Option(LegacyGuice.itemCommentService.getCommentsWithACLCheck(key, null, null, -1))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import com.tle.web.api.item.equella.interfaces.beans.{DisplayField, DisplayOptio
* @param links Item's links.
* @param bookmarkId ID of Bookmark linking to this Item.
* @param isLatestVersion True if this version is the latest version.
* @param drmStatus Status of Item's DRM, consisting if terms accepted and if authorised, absent if item not DRM controlled.
*/
case class SearchResultItem(
uuid: String,
Expand All @@ -61,7 +62,8 @@ case class SearchResultItem(
keywordFoundInAttachment: Boolean,
links: java.util.Map[String, String],
bookmarkId: Option[Long],
isLatestVersion: Boolean
isLatestVersion: Boolean,
drmStatus: Option[DrmStatus]
)

/**
Expand Down Expand Up @@ -89,3 +91,11 @@ case class SearchResultAttachment(
links: java.util.Map[String, String],
filePath: Option[String]
)

/**
* Model class providing DRM related status.
*
* @param termsAccepted Whether terms have been accepted or not.
* @param isAuthorised Whether user is authorised to access Item or accept DRM.
*/
case class DrmStatus(termsAccepted: Boolean, isAuthorised: Boolean)
Loading

0 comments on commit 007e2e1

Please sign in to comment.