Skip to content

Commit

Permalink
Merge pull request #3194 from PenghaiZhang/feature/drm-rest-endpoints
Browse files Browse the repository at this point in the history
Feature/DRM rest endpoints
  • Loading branch information
PenghaiZhang authored Jul 14, 2021
2 parents c0c097d + dd140a4 commit 6b45e4f
Show file tree
Hide file tree
Showing 10 changed files with 451 additions and 2 deletions.
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,6 +18,7 @@

package com.tle.core.item.service;

import com.dytech.edge.exceptions.DRMException;
import com.dytech.edge.wizard.beans.DRMPage;
import com.tle.beans.item.DrmAcceptance;
import com.tle.beans.item.DrmSettings;
Expand All @@ -27,6 +28,7 @@
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.BadRequestException;

/** @author Nicholas Read */
public interface DrmService {
Expand All @@ -46,6 +48,16 @@ boolean hasAcceptedOrRequiresNoAcceptance(

void acceptLicense(Item item);

/**
* Accept DRM terms.
*
* @param item Item which is protected by the DRM.
* @throws DRMException if user is not authorised to accept.
* @throws BadRequestException if user accepted already or does not need to accept.
* @return ID of the new DrmAcceptance.
*/
long acceptLicenseOrThrow(Item item);

void revokeAcceptance(Item item, String userID);

void revokeAllItemAcceptances(Item item);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,26 @@
import com.tle.core.events.UserIdChangedEvent;
import com.tle.core.events.listeners.UserChangeListener;
import com.tle.core.guice.Bind;
import com.tle.core.i18n.CoreStrings;
import com.tle.core.item.dao.DrmAcceptanceDao;
import com.tle.core.item.event.ItemDeletedEvent;
import com.tle.core.item.event.listener.ItemDeletedListener;
import com.tle.core.item.service.DrmService;
import com.tle.core.security.impl.AclExpressionEvaluator;
import com.tle.core.services.user.UserSessionService;
import com.tle.core.settings.service.ConfigurationService;
import com.tle.exceptions.AccessDeniedException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.BadRequestException;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
Expand Down Expand Up @@ -241,6 +245,31 @@ public void acceptLicense(Item item) {
}
}

@Override
@Transactional(propagation = Propagation.REQUIRED)
public long acceptLicenseOrThrow(Item item) {
if (CurrentUser.isGuest()
|| (CurrentUser.wasAutoLoggedIn()
&& configService.getProperties(new AutoLogin()).isTransientDrmAcceptances())) {
throw new AccessDeniedException(CoreStrings.text("drmfilter.drmnotallowedtoaccept"));
}

isAuthorised(item, CurrentUser.getUserState().getIpAddress());
// if 'requiresAcceptance' returns null, it means terms have been accepted or the user does not
// need to
// accept. So throw an exception.
Optional.ofNullable(realAcceptanceCheck(item, false, false))
.orElseThrow(() -> new BadRequestException(CoreStrings.text("drmfilter.drmtermsaccepted")));

// Now we are sure the user is allowed to accept DRM.
DrmAcceptance agreement = new DrmAcceptance();
agreement.setItem(item);
agreement.setUser(CurrentUser.getUserID());
agreement.setDate(new Date());

return dao.save(agreement);
}

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void revokeAcceptance(Item item, String userID) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import com.tle.web.api.LegacyContentApi;
import com.tle.web.api.auth.Auth;
import com.tle.web.api.cloudprovider.CloudProviderApi;
import com.tle.web.api.drm.DrmResource;
import com.tle.web.api.favourite.FavouriteResource;
import com.tle.web.api.institution.AclResource;
import com.tle.web.api.institution.GdprResource;
Expand Down Expand Up @@ -113,6 +114,7 @@ public class RestEasyServlet extends HttpServletDispatcher implements MapperExte
Auth.class,
CloudProviderApi.class,
CloudSearchSettingsResource.class,
DrmResource.class,
FacetedSearchClassificationResource.class,
FavouriteResource.class,
GdprResource.class,
Expand Down
Loading

0 comments on commit 6b45e4f

Please sign in to comment.