Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import java.util.concurrent.CompletionException
import java.io.File
import org.apache.commons.io.FilenameUtils
import javax.inject.Inject
import org.apache.commons.lang3.ObjectUtils
import org.apache.commons.lang3.StringUtils
import org.apache.commons.collections4.{CollectionUtils, MapUtils}
import org.sunbird.`object`.importer.{ImportConfig, ImportManager}
import org.sunbird.actor.core.BaseActor
import org.sunbird.cache.impl.RedisCache
import org.sunbird.content.util.{AcceptFlagManager, ContentConstants, CopyManager, DiscardManager, FlagManager, RetireManager}
import org.sunbird.cloudstore.StorageService
import org.sunbird.common.{ContentParams, Platform, Slug}
import org.sunbird.common.{ContentParams, JsonUtils, Platform, Slug}
import org.sunbird.common.dto.{Request, Response, ResponseHandler}
import org.sunbird.common.exception.ClientException
import org.sunbird.content.dial.DIALManager
Expand Down Expand Up @@ -73,19 +75,35 @@ class ContentActor @Inject() (implicit oec: OntologyEngineContext, ss: StorageSe
DataNode.read(request).map(node => {
val metadata: util.Map[String, AnyRef] = NodeUtil.serialize(node, fields, node.getObjectType.toLowerCase.replace("image", ""), request.getContext.get("version").asInstanceOf[String])
metadata.put("identifier", node.getIdentifier.replace(".img", ""))
val response: Response = ResponseHandler.OK
if (responseSchemaName.isEmpty) {
response.put("content", metadata)
}
else {
response.put(responseSchemaName, metadata)
}
if(!StringUtils.equalsIgnoreCase(metadata.get("visibility").asInstanceOf[String],"Private")) {
response
}
else {
if (StringUtils.equalsIgnoreCase(metadata.get("visibility").asInstanceOf[String],"Private")) {
throw new ClientException("ERR_ACCESS_DENIED", "content visibility is private, hence access denied")
}
var sa = metadata.get("secureSettings")
var securityAttribute : util.Map[String, AnyRef] = new util.HashMap[String, AnyRef]
if(sa.isInstanceOf[String]) {
securityAttribute = JsonUtils.deserialize(sa.asInstanceOf[String], classOf[java.util.Map[String, AnyRef]])
metadata.put("secureSettings", securityAttribute)
} else if (sa.isInstanceOf[util.Map[String, AnyRef]]) {
securityAttribute = metadata.getOrDefault("secureSettings", new util.HashMap[String, AnyRef]).asInstanceOf[util.Map[String, AnyRef]]
}
//var securityAttribute : util.Map[String, AnyRef] = metadata.getOrDefault("secureSettings", new util.HashMap[String, AnyRef]).asInstanceOf[util.Map[String, AnyRef]]
if (MapUtils.isNotEmpty(securityAttribute)) {
var orgList : util.ArrayList[String] = securityAttribute.getOrDefault("organisation", new util.ArrayList[String]).asInstanceOf[util.ArrayList[String]]
if (!CollectionUtils.isEmpty(orgList)) {
//Content should be read by unique org users only.
var userChannelId : String = request.getRequest.getOrDefault("x-user-channel-id", "").asInstanceOf[String]
if (!orgList.contains(userChannelId)) {
throw new ClientException("ERR_ACCESS_DENIED", "User is not allowed to read this content.")
}
}
}
val response: Response = ResponseHandler.OK
if (responseSchemaName.isEmpty) {
response.put("content", metadata)
} else {
response.put(responseSchemaName, metadata)
}
response
})
}

Expand Down
15 changes: 15 additions & 0 deletions content-api/content-service/app/controllers/BaseController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,19 @@ abstract class BaseController(protected val cc: ControllerComponents)(implicit e
Future(BadRequest(JavaJsonUtils.serialize(result)).as("application/json"))
}

def commonReadHeaders(ignoreHeaders: Option[List[String]] = Option(List()))(implicit request: Request[AnyContent]): java.util.Map[String, Object] = {
val customHeaders = Map("x-authenticated-user-orgid" -> "x-user-channel-id", "x-channel-id" -> "channel", "X-Consumer-ID" -> "consumerId", "X-App-Id" -> "appId").filterKeys(key => !ignoreHeaders.getOrElse(List()).contains(key))
customHeaders.map(ch => {
val value = request.headers.get(ch._1)
if (value.isDefined && !value.isEmpty) {
collection.mutable.HashMap[String, Object](ch._2 -> value.get).asJava
} else {
collection.mutable.HashMap[String, Object]().asJava
}
}).reduce((a, b) => {
a.putAll(b)
return a
})
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class ContentController @Inject()(@Named(ActorNames.CONTENT_ACTOR) contentActor:
* @return
*/
def read(identifier: String, mode: Option[String], fields: Option[String]) = Action.async { implicit request =>
val headers = commonHeaders()
val headers = commonReadHeaders()
val content = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
content.putAll(headers)
content.putAll(Map("identifier" -> identifier, "mode" -> mode.getOrElse("read"), "fields" -> fields.getOrElse("")).asJava)
Expand Down Expand Up @@ -94,7 +94,7 @@ class ContentController @Inject()(@Named(ActorNames.CONTENT_ACTOR) contentActor:
}

def getHierarchy(identifier: String, mode: Option[String]) = Action.async { implicit request =>
val headers = commonHeaders()
val headers = commonReadHeaders()
val content = new java.util.HashMap().asInstanceOf[java.util.Map[String, Object]]
content.putAll(headers)
content.putAll(Map("rootId" -> identifier, "mode" -> mode.getOrElse("")).asJava)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import org.apache.commons.lang3.StringUtils
import org.sunbird.cache.impl.RedisCache
import org.sunbird.common.dto.{Request, Response, ResponseHandler, ResponseParams}
import org.sunbird.common.exception.{ClientException, ErrorCodes, ResourceNotFoundException, ResponseCode, ServerException}
import org.sunbird.common.{JsonUtils, Platform}
import org.sunbird.common.{JsonUtils, JWTUtil, Platform}
import org.sunbird.graph.dac.model.Node
import org.sunbird.graph.nodes.DataNode
import org.sunbird.graph.utils.{NodeUtil, ScalaJsonUtils}
Expand Down Expand Up @@ -132,7 +132,9 @@ object HierarchyManager {
}
val bookmarkId = request.get("bookmarkId").asInstanceOf[String]
var metadata: util.Map[String, AnyRef] = NodeUtil.serialize(rootNode, new util.ArrayList[String](), request.getContext.get("schemaName").asInstanceOf[String], request.getContext.get("version").asInstanceOf[String])

if (!validateContentSecurity(request, metadata)) {
Future(ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "User can't read content with Id: " + request.get("rootId")))
}
fetchRelationalMetadata(request, rootNode.getIdentifier).map(collRelationalMetadata => {
val hierarchy = fetchHierarchy(request, rootNode.getIdentifier)

Expand Down Expand Up @@ -211,15 +213,24 @@ object HierarchyManager {
if (!result.isEmpty) {
val bookmarkId = request.get("bookmarkId").asInstanceOf[String]
val rootHierarchy = result.get("content").asInstanceOf[util.Map[String, AnyRef]]
if (StringUtils.isEmpty(bookmarkId)) {
ResponseHandler.OK.put("content", rootHierarchy)
if (!validateContentSecurity(request, rootHierarchy)) {
ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "User can't read content with Id: " + request.get("rootId"))
} else {
val children = rootHierarchy.getOrElse("children", new util.ArrayList[util.Map[String, AnyRef]]()).asInstanceOf[util.List[util.Map[String, AnyRef]]]
val bookmarkHierarchy = filterBookmarkHierarchy(children, bookmarkId)
if (MapUtils.isEmpty(bookmarkHierarchy)) {
ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "bookmarkId " + bookmarkId + " does not exist")
if (isSecureContent(rootHierarchy)) {
val csToken = generateCSToken(rootHierarchy.get("childNodes").asInstanceOf[util.List[String]])
rootHierarchy.put("cstoken", csToken)
}

if (StringUtils.isEmpty(bookmarkId)) {
ResponseHandler.OK.put("content", rootHierarchy)
} else {
ResponseHandler.OK.put("content", bookmarkHierarchy)
val children = rootHierarchy.getOrElse("children", new util.ArrayList[util.Map[String, AnyRef]]()).asInstanceOf[util.List[util.Map[String, AnyRef]]]
val bookmarkHierarchy = filterBookmarkHierarchy(children, bookmarkId)
if (MapUtils.isEmpty(bookmarkHierarchy)) {
ResponseHandler.ERROR(ResponseCode.RESOURCE_NOT_FOUND, ResponseCode.RESOURCE_NOT_FOUND.name(), "bookmarkId " + bookmarkId + " does not exist")
} else {
ResponseHandler.OK.put("content", bookmarkHierarchy)
}
}
}
} else
Expand Down Expand Up @@ -713,4 +724,40 @@ object HierarchyManager {
if(configObjTypes.nonEmpty && !configObjTypes.contains(childNode.getOrDefault("objectType", "").asInstanceOf[String]))
throw new ClientException("ERR_INVALID_CHILDREN", "Invalid Children objectType "+childNode.get("objectType")+" found for : "+childNode.get("identifier") + "| Please provide children having one of the objectType from "+ configObjTypes.asJava)
}

def isSecureContent (metadata: util.Map[String, AnyRef])(implicit ec: ExecutionContext): Boolean = {
var securityAttribute : util.Map[String, AnyRef] = metadata.getOrDefault("secureSettings", new util.HashMap[String, AnyRef]).asInstanceOf[util.Map[String, AnyRef]]
var isSecureContent = false
if (MapUtils.isNotEmpty(securityAttribute)) {
var orgList : util.ArrayList[String] = securityAttribute.getOrDefault("organisation", new util.ArrayList[String]).asInstanceOf[util.ArrayList[String]]
if (!CollectionUtils.isEmpty(orgList)) {
isSecureContent = true
}
}
isSecureContent
}

def validateContentSecurity(request: Request, metadata: util.Map[String, AnyRef])(implicit ec: ExecutionContext): Boolean = {
var securityAttribute : util.Map[String, AnyRef] = metadata.getOrDefault("secureSettings", new util.HashMap[String, AnyRef]).asInstanceOf[util.Map[String, AnyRef]]
var isUserAllowedToRead = true
if (MapUtils.isNotEmpty(securityAttribute)) {
var orgList : util.ArrayList[String] = securityAttribute.getOrDefault("organisation", new util.ArrayList[String]).asInstanceOf[util.ArrayList[String]]
if (!CollectionUtils.isEmpty(orgList)) {
//Content should be read by unique org users only.
var userChannelId : String = request.getRequest.getOrDefault("x-user-channel-id", "").asInstanceOf[String]
if (!orgList.contains(userChannelId)) {
isUserAllowedToRead = false
}
}
}
isUserAllowedToRead
}

def generateCSToken(children: util.List[String])(implicit ec: ExecutionContext): String = {
var csToken = "";
var claimsMap : util.Map[String, AnyRef] = new util.HashMap[String, AnyRef]
claimsMap.put("contentIdentifier", children)
csToken = JWTUtil.createHS256Token(claimsMap)
csToken
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ protected static Map<String, Object> getSystemPropertyQueryMap(Node node, String
if (StringUtils.isBlank(node.getIdentifier()))
node.setIdentifier(Identifier.getIdentifier(node.getGraphId(), Identifier.getUniqueIdFromTimestamp()));

if (node.getMetadata().containsKey("secureSettings")) {
node.setIdentifier(node.getIdentifier() + "_rc");
}
// Adding 'IL_UNIQUE_ID' Property
query.append(
SystemProperties.IL_UNIQUE_ID.name() + ": { SP_" + SystemProperties.IL_UNIQUE_ID.name() + " }, ");
Expand Down
Loading