Skip to content

Commit

Permalink
Merge branch 'hotfix/2019.1.4' into stable-2019.1
Browse files Browse the repository at this point in the history
  • Loading branch information
PenghaiZhang committed Mar 2, 2020
2 parents 53c4133 + 0d85cc4 commit 4ffc287
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import com.tle.core.db._
import com.tle.core.httpclient._
import com.tle.core.oauthclient.OAuthClientService
import fs2.Stream
import org.apache.commons.lang.RandomStringUtils
import org.slf4j.LoggerFactory

import scala.collection.JavaConverters._
Expand Down Expand Up @@ -87,15 +88,22 @@ object CloudProviderService {
IO.fromEither(
UriTemplateService.replaceVariables(serviceUri.url, provider.baseUrl, cparams ++ params))
}
req = f(uri)
req = f(uri)
requestContext = "[" + RandomStringUtils.randomAlphanumeric(6) + "] provider: " + provider.id + ", vendor: " + provider.vendorId
_ = Logger.debug(
requestContext + ", method: " + req.method.m + ", request: " + uri.toString.split('?')(0))
auth = provider.providerAuth
response <- if (serviceUri.authenticated) {
dbLiftIO.liftIO(tokenUrlForProvider(provider)).flatMap { oauthUrl =>
OAuthClientService
.authorizedRequest(oauthUrl.toString, auth.clientId, auth.clientSecret, req)
}
} else dbLiftIO.liftIO(req.send())
} yield response

} yield {
Logger.debug(requestContext + ", response status: " + response.code)
response
}

case class ControlListCacheValue(
expiry: Instant,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ import io.lemonlabs.uri.{Path => _, _}
import io.swagger.annotations.Api
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
import javax.ws.rs._
import javax.ws.rs.core.Response.ResponseBuilder
import javax.ws.rs.core.Response.{ResponseBuilder, Status}
import javax.ws.rs.core.{CacheControl, Context, Response, UriInfo}
import org.slf4j.LoggerFactory

import scala.collection.JavaConverters._
import scala.collection.mutable
Expand Down Expand Up @@ -104,6 +105,33 @@ object LegacyContentController extends AbstractSectionsController with SectionFi

import LegacyGuice.urlService

def isClientPath(relUrl: RelativeUrl): Boolean = {
// This regex matches the relative url of Item Summary page
// For example 'items/95075bdd-4049-46ab-a1aa-043902e239a3/3/'
// The last forward slash does not exist in some cases
val itemSummaryUrlPattern =
"items\\/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\\/\\d+\\/?".r

// This regex explicitly matches the relative Url of logon
// For example, 'logon.do' or 'logon.do?.page=home.do'
val logonUrlPattern = "logon\\.do\\??.*".r

// This regex matches the relative Urls of other pages
// For example, 'home.do' or 'access/runwizard.do?.wizid...'
val otherUrlPattern = ".+\\.do\\??.*".r

relUrl.toString() match {
case logonUrlPattern() => false
case itemSummaryUrlPattern() => true
case otherUrlPattern() => true
case _ => false
}
}

def internalRoute(uri: String): Option[String] = {
relativeURI(uri).filter(isClientPath).map(r => "/" + r.toString())
}

def relativeURI(uri: String): Option[RelativeUrl] = {
val baseUrl = AbsoluteUrl.parse(urlService.getBaseInstitutionURI.toString)
val Host = baseUrl.host
Expand Down Expand Up @@ -208,6 +236,7 @@ object LegacyContentController extends AbstractSectionsController with SectionFi
@Api("Legacy content")
@Path("content")
class LegacyContentApi {
val LOGGER = LoggerFactory.getLogger(classOf[LegacyContentApi])

def parsePath(path: String): (String, MutableSectionInfo => MutableSectionInfo) = {

Expand All @@ -220,6 +249,7 @@ class LegacyContentApi {
info
})
}

path match {
case "" => ("/home.do", identity)
case p if p.startsWith("items/") => itemViewer(p.substring("items/".length), (_, vi) => vi)
Expand Down Expand Up @@ -316,15 +346,13 @@ class LegacyContentApi {
new BookmarkAndModify(context,
menuLink.getHandlerMap.getHandler("click").getModifier))
.getHref
val relativized =
LegacyContentController.relativeURI(href).filter(_.path.parts.last.endsWith(".do"))
val route = Option(mc.getRoute)
val route = Option(mc.getRoute).orElse(LegacyContentController.internalRoute(href))
val iconUrl = if (mc.isCustomImage) Some(mc.getBackgroundImagePath) else None
MenuItem(
menuLink.getLabelText,
if (relativized.isEmpty && route.isEmpty) Some(href) else None,
if (route.isEmpty) Some(href) else None,
Option(mc.getSystemIcon),
route.orElse(relativized.map(r => "/" + r.toString)),
route,
iconUrl,
"_blank" == menuLink.getTarget
)
Expand Down Expand Up @@ -428,9 +456,9 @@ class LegacyContentApi {
Option(req.getAttribute(LegacyContentController.RedirectedAttr).asInstanceOf[String]).map {
url =>
Response.ok {
LegacyContentController.relativeURI(url) match {
case None => ExternalRedirect(url)
case Some(relative) => InternalRedirect(relative.toString, userChanged(req))
LegacyContentController.internalRoute(url) match {
case Some(relative) => InternalRedirect(relative.substring(1), userChanged(req))
case _ => ExternalRedirect(url)
}
}
}
Expand Down Expand Up @@ -563,7 +591,7 @@ class LegacyContentApi {
.map(bbr => SectionUtils.renderToString(context, bbr.getRenderable))
}

def ajaxResponse(info: MutableSectionInfo, arc: AjaxRenderContext) = {
def ajaxResponse(info: MutableSectionInfo, arc: AjaxRenderContext): Response.ResponseBuilder = {
var resp: ResponseBuilder = null
val context = LegacyContentController.prepareJSContext(info)

Expand Down Expand Up @@ -596,6 +624,10 @@ class LegacyContentApi {
case tr: TemplateResult => tr.getNamedResult(context, "body")
case sr: SectionRenderable => sr
case pr: PreRenderable => new PreRenderOnly(pr)
//Due to many unknowns of what could cause renderedBody being null, return a 500 error at the moment.
case _ =>
LOGGER.debug("Unknown error at renderedBody - ajaxResponse");
return Response.status(Status.NOT_IMPLEMENTED);
}
renderAjaxBody(renderedBody)
val responseCallback = arc.getJSONResponseCallback
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,33 @@ class WizardApi {

}

private def getStreamedBody(content: InputStream): Stream[IO, ByteBuffer] = {
readInputStream(IO(content), 4096, Implicits.global).chunks.map(_.toByteBuffer)
}

private def getRequestHeaders(req: HttpServletRequest): Map[String, String] = {
val headers = (for {
headerName <- req.getHeaderNames.asScala
} yield (headerName, req.getHeader(headerName))).toMap

val filterCookies = {
val filterList = List("JSESSIONID")
val cookies =
req.getCookies.filter(cookie => filterList.exists(!_.startsWith(cookie.getName)))
// Generate a string which include cookie pairs separated by a semi-colon
cookies.map(cookie => s"${cookie.getName}=${cookie.getValue}").mkString(";")
}
// If have cookies apart from those unneeded then reset cookie in the header; otherwise remove cookie from the header.
val filterHeaders = {
if (!filterCookies.isEmpty) {
headers + ("cookie" -> filterCookies)
} else {
headers - "cookie"
}
}
filterHeaders - "host"
}

@NoCache
@GET
@Path("provider/{providerId}/{serviceId}")
Expand All @@ -192,7 +219,9 @@ class WizardApi {
@PathParam("serviceId") serviceId: String,
@Context uriInfo: UriInfo,
@Context req: HttpServletRequest): Response = {
proxyRequest(wizid, req, providerId, serviceId, uriInfo)(sttp.get)
proxyRequest(wizid, req, providerId, serviceId, uriInfo) { uri =>
sttp.get(uri).headers(getRequestHeaders(req))
}
}

@POST
Expand All @@ -203,12 +232,39 @@ class WizardApi {
@Context uriInfo: UriInfo,
@Context req: HttpServletRequest,
content: InputStream): Response = {
val streamedBody =
readInputStream(IO(content), 4096, Implicits.global).chunks.map(_.toByteBuffer)
proxyRequest(wizid, req, providerId, serviceId, uriInfo) { uri =>
sttp
.post(uri)
.streamBody(streamedBody)
.headers(getRequestHeaders(req))
.streamBody(getStreamedBody(content))
}
}

@PUT
@Path("provider/{providerId}/{serviceId}")
def proxyPUT(@PathParam("wizid") wizid: String,
@PathParam("providerId") providerId: UUID,
@PathParam("serviceId") serviceId: String,
@Context uriInfo: UriInfo,
@Context req: HttpServletRequest,
content: InputStream): Response = {
proxyRequest(wizid, req, providerId, serviceId, uriInfo) { uri =>
sttp
.put(uri)
.headers(getRequestHeaders(req))
.streamBody(getStreamedBody(content))
}
}

@DELETE
@Path("provider/{providerId}/{serviceId}")
def proxyDELETE(@PathParam("wizid") wizid: String,
@PathParam("providerId") providerId: UUID,
@PathParam("serviceId") serviceId: String,
@Context uriInfo: UriInfo,
@Context req: HttpServletRequest): Response = {
proxyRequest(wizid, req, providerId, serviceId, uriInfo) { uri =>
sttp.delete(uri).headers(getRequestHeaders(req))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package com.tle.core.favourites.service;

import com.dytech.edge.web.WebConstants;
import com.tle.beans.Institution;
import com.tle.common.Check;
import com.tle.common.institution.CurrentInstitution;
Expand All @@ -37,6 +38,7 @@
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang.StringUtils;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.springframework.transaction.annotation.Transactional;
Expand Down Expand Up @@ -68,7 +70,23 @@ public void deleteById(long id) {
public void executeSearch(SectionInfo info, long id) {
FavouriteSearch search = dao.getById(id);
if (search != null) {
SectionInfo forward = info.createForwardForUri(search.getUrl());
String url = search.getUrl();
// When user favourites a normal search, cloud search or hierarchy search,
// the value of 'url' starts with '/access' if the fav search is added in old oEQ versions,
// which results in "no tree for xxx" error. Hence, remove "/access" if it exists.
if (url != null
&& url.startsWith(WebConstants.ACCESS_PATH)
&& StringUtils.indexOfAny(
url,
new String[] {
WebConstants.SEARCHING_PAGE,
WebConstants.HIERARCHY_PAGE,
WebConstants.CLOUDSEARCH_PAGE
})
> -1) {
url = "/" + url.replaceFirst(WebConstants.ACCESS_PATH, "");
}
SectionInfo forward = info.createForwardForUri(url);
info.forwardAsBookmark(forward);
}
}
Expand Down
74 changes: 48 additions & 26 deletions autotest/IntegTester/ps/www/control.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as ReactDOM from "react-dom";
import * as React from "react";
import axios from "axios";
import axios, { AxiosResponse } from "axios";

import {
ControlApi,
Expand Down Expand Up @@ -93,6 +93,28 @@ function TestControl(p: ControlApi<MyConfig>) {
[failValidation, required]
);

interface TestingButtonProps {
buttonName: string;
onClick: () => Promise<AxiosResponse<any>>;
}
const TestingButton = TestingButtonProps => (
<button
onClick={() => {
TestingButtonProps.onClick()
.then(resp => setServiceResponse(resp.data))
.catch((err: Error) => {
setServiceResponse(err.message);
});
}}
>
{TestingButtonProps.buttonName}
</button>
);

const requestUrl = (query: String = queryString) => {
return p.providerUrl(serviceId) + "?" + query;
};

React.useEffect(() => {
p.registerValidator(validator);
return () => p.deregisterValidator(validator);
Expand Down Expand Up @@ -135,14 +157,6 @@ function TestControl(p: ControlApi<MyConfig>) {
onChange={e => setQueryString(e.target.value)}
/>
</div>
<div>
POST Request?{" "}
<input
type="checkbox"
checked={postRequest}
onChange={e => setPostRequest(e.target.checked)}
/>
</div>
{postRequest && (
<div>
Payload:
Expand All @@ -165,23 +179,31 @@ function TestControl(p: ControlApi<MyConfig>) {
/>
</div>
)}
<button
onClick={_ => {
const url = p.providerUrl(serviceId) + "?" + queryString;
const req = postRequest
? axios.post(url, {
data: serviceContent
})
: axios.get(url);
return req
.then(resp => setServiceResponse(resp.data))
.catch((err: Error) => {
setServiceResponse(err.message);
});
}}
>
Execute
</button>

<TestingButton buttonName="GET" onClick={() => axios.get(requestUrl())} />

<TestingButton
buttonName="POST"
onClick={() =>
axios.post(requestUrl(), {
data: serviceContent
})
}
/>

<TestingButton
buttonName="DELETE"
onClick={() => axios.delete(requestUrl("param1=test_param_one"))}
/>

<TestingButton
buttonName="PUT"
onClick={() =>
axios.put(requestUrl(), {
data: serviceContent
})
}
/>
</div>
);

Expand Down
Loading

0 comments on commit 4ffc287

Please sign in to comment.