diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf index 8afb2f186c..1f710f368d 100644 --- a/src/main/resources/reference.conf +++ b/src/main/resources/reference.conf @@ -208,6 +208,7 @@ mydata = { purpose = "Tietoja käytetään opiskelijahintaisten matkalippujen myöntämiseen." membercodes = ["2769790-1"] # Identify API caller subsystemcodes = ["koski"] # Unused + orgOid = "1.2.246.562.10.77876988401" # Mydata use is interpreted based on this from auditlogs }, { id = "frank" @@ -215,6 +216,7 @@ mydata = { purpose = "" membercodes = ["2769790-2"] subsystemcodes = ["koski"] + orgOid = "1.2.246.562.10.46399742280" }, ] callbackURLs = [ diff --git a/src/main/scala/fi/oph/koski/mydata/MyDataConfig.scala b/src/main/scala/fi/oph/koski/mydata/MyDataConfig.scala index 661e222745..776c01e434 100644 --- a/src/main/scala/fi/oph/koski/mydata/MyDataConfig.scala +++ b/src/main/scala/fi/oph/koski/mydata/MyDataConfig.scala @@ -40,4 +40,9 @@ trait MyDataConfig extends Logging { ) } + def isMyDataOrg(orgOid: String): Boolean = { + conf.getConfigList("members").asScala.exists(member => + member.getString("orgOid") == orgOid + ) + } } diff --git a/src/main/scala/fi/oph/koski/omaopintopolkuloki/AuditLogService.scala b/src/main/scala/fi/oph/koski/omaopintopolkuloki/AuditLogService.scala index 8f68d8945c..fe26f28b2d 100644 --- a/src/main/scala/fi/oph/koski/omaopintopolkuloki/AuditLogService.scala +++ b/src/main/scala/fi/oph/koski/omaopintopolkuloki/AuditLogService.scala @@ -7,20 +7,25 @@ import fi.oph.koski.organisaatio.Opetushallitus import fi.oph.koski.http.{HttpStatus, KoskiErrorCategory} import fi.oph.koski.json.JsonSerializer import fi.oph.koski.log.Logging +import fi.oph.koski.mydata.MyDataConfig import fi.oph.koski.schema.LocalizedString import fi.oph.koski.omaopintopolkuloki.AuditLogDynamoDB.AuditLogTableName import software.amazon.awssdk.services.dynamodb.model.{AttributeValue, QueryRequest} import scala.collection.JavaConverters._ -class AuditLogService(app: KoskiApplication) extends Logging { - private val organisaatioRepository = app.organisaatioRepository - private val dynamoDB = AuditLogDynamoDB.buildDb(app.config) +class AuditLogService(val application: KoskiApplication) extends Logging with MyDataConfig { + private val organisaatioRepository = application.organisaatioRepository + private val dynamoDB = AuditLogDynamoDB.buildDb(application.config) def queryLogsFromDynamo(oppijaOid: String): Either[HttpStatus, Seq[OrganisaationAuditLogit]] = { runQuery(oppijaOid).flatMap(results => HttpStatus.foldEithers(buildLogs(results).toSeq)) } + def queryLogsFromDynamoV2(oppijaOid: String): Either[HttpStatus, Seq[OrganisaationAuditLogitV2]] = { + runQueryV2(oppijaOid).flatMap(results => HttpStatus.foldEithers(buildLogsV2(results).toSeq)) + } + private def runQuery(oppijaOid: String): Either[HttpStatus, Seq[util.Map[String, AttributeValue]]] = { val querySpec = QueryRequest.builder .tableName(AuditLogTableName) @@ -45,6 +50,51 @@ class AuditLogService(app: KoskiApplication) extends Logging { } } } + + private def runQueryV2(oppijaOid: String): Either[HttpStatus, Seq[util.Map[String, AttributeValue]]] = { + val querySpec = QueryRequest.builder + .tableName(AuditLogTableName) + .keyConditionExpression("studentOid = :oid") + .filterExpression( + """not contains (organizationOid, :self) and + | (contains (#rawEntry, :katsominen) or + | contains (#rawEntry, :muutoshistoria_katsominen) or + | contains (#rawEntry, :ytr_katsominen) or + | contains (#rawEntry, :oauth2_katsominen_kaikki_tiedot) or + | contains (#rawEntry, :oauth2_katsominen_suoritetut_tutkinnot) or + | contains (#rawEntry, :oauth2_katsominen_aktiiviset_ja_paattyneet_opinnot) or + | contains (#rawEntry, :suoritusjako_katsominen) or + | contains (#rawEntry, :suoritusjako_katsominen_suoritetut_tutkinnot) or + | contains (#rawEntry, :oauth2_katsominen_aktiiviset_ja_paattyneet_opinnot) or + | contains(#rawEntry, :varda_service)) + | """.stripMargin) + .expressionAttributeNames(Map("#rawEntry" -> "raw").asJava) + .expressionAttributeValues({ + val valueMap = new util.HashMap[String, AttributeValue]() + valueMap.put(":oid", AttributeValue.builder.s(oppijaOid).build) + valueMap.put(":self", AttributeValue.builder.s("self").build) + valueMap.put(":katsominen", AttributeValue.builder.s("\"OPISKELUOIKEUS_KATSOMINEN\"").build) + valueMap.put(":muutoshistoria_katsominen", AttributeValue.builder.s("\"MUUTOSHISTORIA_KATSOMINEN\"").build) + valueMap.put(":ytr_katsominen", AttributeValue.builder.s("\"YTR_OPISKELUOIKEUS_KATSOMINEN\"").build) + valueMap.put(":suoritusjako_katsominen", AttributeValue.builder.s("\"KANSALAINEN_SUORITUSJAKO_KATSOMINEN\"").build) + valueMap.put(":suoritusjako_katsominen_suoritetut_tutkinnot", AttributeValue.builder.s("\"KANSALAINEN_SUORITUSJAKO_KATSOMINEN_SUORITETUT_TUTKINNOT\"").build) + valueMap.put(":suoritusjako_katsominen_aktiiviset_ja_paattyneet_opinnot", AttributeValue.builder.s("\"KANSALAINEN_SUORITUSJAKO_KATSOMINEN_AKTIIVISET_JA_PAATTYNEET_OPINNOT\"").build) + valueMap.put(":oauth2_katsominen_kaikki_tiedot", AttributeValue.builder.s("\"OAUTH2_KATSOMINEN_KAIKKI_TIEDOT\"").build) + valueMap.put(":oauth2_katsominen_suoritetut_tutkinnot", AttributeValue.builder.s("\"OAUTH2_KATSOMINEN_SUORITETUT_TUTKINNOT\"").build) + valueMap.put(":oauth2_katsominen_aktiiviset_ja_paattyneet_opinnot", AttributeValue.builder.s("\"OAUTH2_KATSOMINEN_AKTIIVISET_JA_PAATTYNEET_OPINNOT\"").build) + valueMap.put(":varda_service", AttributeValue.builder.s("\"varda\"").build) + valueMap + }) + + try { + Right(dynamoDB.query(querySpec.build()).items().asScala) + } catch { + case e: Exception => { + logger.error(e)(s"AuditLogien haku epäonnistui oidille $oppijaOid") + Left(KoskiErrorCategory.internalError()) + } + } + } private def convertToAuditLogRow(item: util.Map[String, AttributeValue]): AuditlogRow = { val organizationOid = item.asScala.view.collectFirst { case ("organizationOid", value) if value.l() != null => @@ -80,6 +130,27 @@ class AuditLogService(app: KoskiApplication) extends Logging { } } + // Luokitteluun käliä varten tarvittavat lisäparametrit: + // (1) Onko suostumusperustainen? Tehdään HSL:lle ja Frank:lle tällä hetkellä kälissä organisaatio-oidin perusteella, tämän logiikan voisi + // siirtää myös tähän. Ja OAUTH2_KATSOMINEN_* aina suostumusperustaista. + private def buildLogsV2(queryResults: Seq[util.Map[String, AttributeValue]]): Iterable[Either[HttpStatus, OrganisaationAuditLogitV2]] = { + val timestampsGroupedByListOfOidsAndServiceName = queryResults.map(item => { + val parsedRow = convertToAuditLogRow(item) + val parsedRaw = JsonSerializer.parse[AuditlogRaw](parsedRow.raw, ignoreExtras = true) + val organisaatioOidit = parsedRow.organizationOid.sorted + val timestampString = parsedRow.time + val serviceName = parsedRaw.serviceName + val isMyDataUse = parsedRaw.operation.startsWith("OAUTH2_KATSOMINEN") || parsedRow.organizationOid.headOption.exists(isMyDataOrg) + val isJakolinkkiUse = parsedRaw.operation.startsWith("KANSALAINEN_SUORITUSJAKO_KATSOMINEN") + (organisaatioOidit, serviceName, isMyDataUse, isJakolinkkiUse, timestampString) + }).groupBy(x => (x._1, x._2, x._3, x._4)).mapValues(_.map(_._5)) + + timestampsGroupedByListOfOidsAndServiceName.map { case ((orgs, serviceName, isMyDataUse, isJakolinkkiUse), timestamps) => + HttpStatus.foldEithers(orgs.map(toOrganisaatio)) + .map(orgs => OrganisaationAuditLogitV2(orgs, serviceName, isMyDataUse, isJakolinkkiUse, timestamps)) + } + } + private def toOrganisaatio(oid: String): Either[HttpStatus, Organisaatio] = { val nimi = organisaatioRepository.getOrganisaatio(oid) .flatMap(_.nimi) @@ -105,7 +176,8 @@ case class AuditlogRow ( time: String ) case class AuditlogRaw ( - serviceName: String + serviceName: String, + operation: String ) case class OrganisaationAuditLogit( @@ -118,3 +190,11 @@ case class Organisaatio( oid: String, name: LocalizedString ) + +case class OrganisaationAuditLogitV2( + organizations: Seq[Organisaatio], + serviceName: String, + isMyDataUse: Boolean, + isJakolinkkiUse: Boolean, + timestamps: Seq[String] +) diff --git a/src/main/scala/fi/oph/koski/omaopintopolkuloki/OmaOpintoPolkuLokiServlet.scala b/src/main/scala/fi/oph/koski/omaopintopolkuloki/OmaOpintoPolkuLokiServlet.scala index 58735930bd..c8672f176f 100644 --- a/src/main/scala/fi/oph/koski/omaopintopolkuloki/OmaOpintoPolkuLokiServlet.scala +++ b/src/main/scala/fi/oph/koski/omaopintopolkuloki/OmaOpintoPolkuLokiServlet.scala @@ -37,6 +37,28 @@ class OmaOpintoPolkuLokiServlet(implicit val application: KoskiApplication) exte })() } + post("/auditlogsV2") { + withJsonBody({ body => + val request = JsonSerializer.extract[AuditlogRequest](body) + val requestedHetu = request.hetu + + val personOid: String = session.user.huollettavat.toList.flatMap { + case r: HuollettavienHakuOnnistui => r.huollettavat + .filter(_.hetu.exists(requestedHetu.contains)) + .map(h => h.oid) + .flatMap { + case o: Some[String] => List(o.get) + case _ => List.empty + } + case _ => List.empty + }.headOption.getOrElse(session.oid) + + renderEither( + auditLogs.queryLogsFromDynamoV2(personOid) + ) + })() + } + get("/whoami") { application.opintopolkuHenkilöFacade.findOppijaByOid(session.oid).map(h => { val huollettavat: List[OmaOpintopolkuLokiHenkiloTiedot] = session.user.huollettavat.toList.flatMap {