Skip to content

Commit

Permalink
Merge pull request #3241 from Opetushallitus/TOR-2210-token-info-in-r…
Browse files Browse the repository at this point in the history
…esponse

TOR-2210 Lisää tokenin tiedot vastauksiin
  • Loading branch information
a544jh authored Dec 13, 2024
2 parents 58a8e35 + 51285b2 commit 480498b
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 40 deletions.
6 changes: 5 additions & 1 deletion src/main/resources/documentation/omadata_oauth2.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,11 @@ Esimerkki palautettavan datan rakenteesta:
}
},
...
]
],
"tokenInfo": {
"scope": "HENKILOTIEDOT_KAIKKI_TIEDOT OPISKELUOIKEUDET_KAIKKI_TIEDOT",
"expirationTime": "2024-12-12T15:16:12.474651+02:00"
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import org.scalatra.ContentEncodingSupport
import org.scalatra.forms._
import org.scalatra.i18n.I18nSupport

import java.time.Instant
import java.time.temporal.ChronoUnit

class OmaDataOAuth2AuthorizationServerServlet(implicit val application: KoskiApplication)
extends KoskiSpecificApiServlet
with Logging with ContentEncodingSupport with NoCache with FormSupport with I18nSupport with RequiresOmaDataOAuth2 with OmaDataOAuth2Support {
Expand Down Expand Up @@ -40,7 +43,11 @@ class OmaDataOAuth2AuthorizationServerServlet(implicit val application: KoskiApp
allowedScopes = koskiSession.omaDataOAuth2Scopes
) match {
case Left(error) => error.getAccessTokenErrorResponse
case Right(successResponse) => successResponse
case Right(accessTokenInfo: AccessTokenInfo) => AccessTokenSuccessResponse(
access_token = accessTokenInfo.accessToken,
token_type = "Bearer",
expires_in = Instant.now().until(accessTokenInfo.expirationTime, ChronoUnit.SECONDS)
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ object OmaDataOAuth2KaikkiOpiskeluoikeudet {

case class OmaDataOAuth2KaikkiOpiskeluoikeudet(
henkilö: OmaDataOAuth2Henkilötiedot,
opiskeluoikeudet: List[Opiskeluoikeus]
opiskeluoikeudet: List[Opiskeluoikeus],
tokenInfo: OmaDataOAuth2TokenInfo
)

object OmaDataOAuth2SuoritetutTutkinnot {
Expand All @@ -28,7 +29,8 @@ object OmaDataOAuth2SuoritetutTutkinnot {

case class OmaDataOAuth2SuoritetutTutkinnot(
henkilö: OmaDataOAuth2Henkilötiedot,
opiskeluoikeudet: List[SuoritetutTutkinnotOpiskeluoikeus]
opiskeluoikeudet: List[SuoritetutTutkinnotOpiskeluoikeus],
tokenInfo: OmaDataOAuth2TokenInfo
)

object OmaDataOAuth2AktiivisetJaPäättyneetOpiskeluoikeudet {
Expand All @@ -38,7 +40,8 @@ object OmaDataOAuth2AktiivisetJaPäättyneetOpiskeluoikeudet {

case class OmaDataOAuth2AktiivisetJaPäättyneetOpiskeluoikeudet(
henkilö: OmaDataOAuth2Henkilötiedot,
opiskeluoikeudet: List[AktiivisetJaPäättyneetOpinnotOpiskeluoikeus]
opiskeluoikeudet: List[AktiivisetJaPäättyneetOpinnotOpiskeluoikeus],
tokenInfo: OmaDataOAuth2TokenInfo
)

object OmaDataOAuth2Henkilötiedot {
Expand Down Expand Up @@ -155,3 +158,8 @@ case class OmaDataOAuth2Henkilötiedot(
hetu: Option[String] = None,
syntymäaika: Option[LocalDate] = None
)

case class OmaDataOAuth2TokenInfo(
scope: String,
expirationTime: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ package fi.oph.koski.omadataoauth2

import fi.oph.koski.db.KoskiTables.{OAuth2Jako, OAuth2JakoKaikki}
import fi.oph.koski.db.PostgresDriverWithJsonSupport.api._
import fi.oph.koski.db.{DB, DatabaseExecutionContext, KoskiTables, OAuth2JakoRow, QueryMethods}
import fi.oph.koski.db._
import fi.oph.koski.log.Logging
import fi.oph.koski.omadataoauth2.OmaDataOAuth2Security.{generateSecret, sha256}

import java.sql.Timestamp
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit
import java.time.{Instant, LocalDateTime}

class OmaDataOAuth2Repository(val db: DB) extends DatabaseExecutionContext with Logging with QueryMethods {
def create(
Expand Down Expand Up @@ -89,20 +88,17 @@ class OmaDataOAuth2Repository(val db: DB) extends DatabaseExecutionContext with
val warning = OmaDataOAuth2Error(OmaDataOAuth2ErrorType.invalid_scope, s"scope=${tooWideScopes.mkString(" ")} exceeds the rights granted to the client ${row.clientId}")
logger.warn(warning.getLoggedErrorMessage)
DBIO.successful(Left(warning))
case Some(row) =>
case Some(row) =>
updateRow(
rows = rows,
accessTokenSHA256 = accessTokenAttemptSHA256.get
).flatMap {
case 1 => DBIO.successful(Right(
AccessTokenInfo(
AccessTokenSuccessResponse(
accessTokenAttempt,
"Bearer",
LocalDateTime.now.until(row.voimassaAsti.toLocalDateTime, ChronoUnit.SECONDS).max(0)
),
row.oppijaOid,
row.scope
accessToken = accessTokenAttempt,
expirationTime = row.voimassaAsti.toInstant,
oppijaOid = row.oppijaOid,
scope = row.scope
)
))
case _ => {
Expand Down Expand Up @@ -141,16 +137,13 @@ class OmaDataOAuth2Repository(val db: DB) extends DatabaseExecutionContext with
val warning = OmaDataOAuth2Error(OmaDataOAuth2ErrorType.invalid_scope, s"scope=${tooWideScopes.mkString(" ")} exceeds the rights granted to the client ${row.clientId}")
logger.warn(warning.getLoggedErrorMessage)
Left(warning)
case Some(row) =>
case Some(row) =>
Right(
AccessTokenInfo(
AccessTokenSuccessResponse(
accessToken,
"Bearer",
LocalDateTime.now.until(row.voimassaAsti.toLocalDateTime, ChronoUnit.SECONDS).max(0)
),
row.oppijaOid,
row.scope
accessToken = accessToken,
expirationTime = row.voimassaAsti.toInstant,
oppijaOid = row.oppijaOid,
scope = row.scope
)
)
}
Expand Down Expand Up @@ -208,7 +201,8 @@ class OmaDataOAuth2Repository(val db: DB) extends DatabaseExecutionContext with
}

case class AccessTokenInfo(
successResponse: AccessTokenSuccessResponse,
accessToken: String,
expirationTime: Instant,
oppijaOid: String,
scope: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import fi.oph.koski.log.KoskiOperation.{OAUTH2_KATSOMINEN_AKTIIVISET_JA_PAATTYNE
import fi.oph.koski.log.{AuditLog, KoskiAuditLogMessage, KoskiOperation, Logging}
import fi.oph.koski.servlet.{KoskiSpecificApiServlet, NoCache}
import org.scalatra.ContentEncodingSupport

import java.time.ZoneId
import java.time.format.DateTimeFormatter
import scala.reflect.runtime.universe.TypeTag

class OmaDataOAuth2ResourceServerServlet(implicit val application: KoskiApplication) extends KoskiSpecificApiServlet
Expand All @@ -30,28 +33,28 @@ class OmaDataOAuth2ResourceServerServlet(implicit val application: KoskiApplicat
expectedClientId = koskiSession.user.username,
allowedScopes = koskiSession.omaDataOAuth2Scopes
) match {
case Right(AccessTokenInfo(_, oppijaOid, scope)) =>
renderOpinnot(oppijaOid, scope)
case Right(AccessTokenInfo(_, tokenExpirationTime, oppijaOid, scope)) =>
renderOpinnot(oppijaOid, scope, tokenExpirationTime.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME))
case Left(error) =>
val errorResult = error.getAccessTokenErrorResponse
renderErrorWithStatus(errorResult, errorResult.httpStatus)
}
}

private def renderOpinnot(oppijaOid: String, scope: String): Unit = {
private def renderOpinnot(oppijaOid: String, scope: String, tokenExpirationTime: String): Unit = {
val overrideSession = KoskiSpecificSession.oauth2KatsominenUser(request)

scope.split(" ").filter(_.startsWith("OPISKELUOIKEUDET_")).toSeq match {
case Seq("OPISKELUOIKEUDET_SUORITETUT_TUTKINNOT") =>
val oppija = application.omaDataOAuth2Service.findSuoritetutTutkinnot(oppijaOid, scope, overrideSession)
val oppija = application.omaDataOAuth2Service.findSuoritetutTutkinnot(oppijaOid, scope, overrideSession, tokenExpirationTime)
auditLogKatsominen(OAUTH2_KATSOMINEN_SUORITETUT_TUTKINNOT, koskiSession.user.username, koskiSession, oppijaOid, scope)
renderOppijaData(oppija)
case Seq("OPISKELUOIKEUDET_AKTIIVISET_JA_PAATTYNEET_OPINNOT") =>
val oppija = application.omaDataOAuth2Service.findAktiivisetJaPäättyneetOpinnot(oppijaOid, scope, overrideSession)
val oppija = application.omaDataOAuth2Service.findAktiivisetJaPäättyneetOpinnot(oppijaOid, scope, overrideSession, tokenExpirationTime)
auditLogKatsominen(OAUTH2_KATSOMINEN_AKTIIVISET_JA_PAATTYNEET_OPINNOT, koskiSession.user.username, koskiSession, oppijaOid, scope)
renderOppijaData(oppija)
case Seq("OPISKELUOIKEUDET_KAIKKI_TIEDOT") =>
val oppija = application.omaDataOAuth2Service.findKaikkiTiedot(oppijaOid, scope, overrideSession)
val oppija = application.omaDataOAuth2Service.findKaikkiTiedot(oppijaOid, scope, overrideSession, tokenExpirationTime)
auditLogKatsominen(OAUTH2_KATSOMINEN_KAIKKI_TIEDOT, koskiSession.user.username, koskiSession, oppijaOid, scope)
renderOppijaData(oppija)
case _ =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class OmaDataOAuth2Service(oauth2Repository: OmaDataOAuth2Repository, val applic
expectedRedirectUri: Option[String],
koskiSession: KoskiSpecificSession,
allowedScopes: Set[String]
): Either[OmaDataOAuth2Error, AccessTokenSuccessResponse] = {
): Either[OmaDataOAuth2Error, AccessTokenInfo] = {
oauth2Repository.createAccessTokenForCode(code, expectedClientId, expectedCodeChallenge, expectedRedirectUri, allowedScopes)
.tap(response =>
AuditLog.log(KoskiAuditLogMessage(OAUTH2_ACCESS_TOKEN_LUONTI, koskiSession, Map(
Expand All @@ -64,7 +64,6 @@ class OmaDataOAuth2Service(oauth2Repository: OmaDataOAuth2Repository, val applic
omaDataOAuth2Scope -> response.scope
)))
)
.map(_.successResponse)
}

def getByAccessToken(
Expand All @@ -75,14 +74,15 @@ class OmaDataOAuth2Service(oauth2Repository: OmaDataOAuth2Repository, val applic
oauth2Repository.getByAccessToken(accessToken, expectedClientId, allowedScopes)
}

def findSuoritetutTutkinnot(oppijaOid: String, scope: String, overrideSession: KoskiSpecificSession): Either[HttpStatus, OmaDataOAuth2SuoritetutTutkinnot] = {
def findSuoritetutTutkinnot(oppijaOid: String, scope: String, overrideSession: KoskiSpecificSession, tokenExpirationTime: String): Either[HttpStatus, OmaDataOAuth2SuoritetutTutkinnot] = {
application.suoritetutTutkinnotService.findSuoritetutTutkinnotOppija(
oppijaOid,
merkitseSuoritusjakoTehdyksi = false
)(overrideSession).map(oppija => {
OmaDataOAuth2SuoritetutTutkinnot(
henkilö = OmaDataOAuth2Henkilötiedot(oppija.henkilö, scope),
opiskeluoikeudet = oppija.opiskeluoikeudet
opiskeluoikeudet = oppija.opiskeluoikeudet,
tokenInfo = OmaDataOAuth2TokenInfo(scope, tokenExpirationTime)
)
})
}
Expand Down Expand Up @@ -125,24 +125,26 @@ class OmaDataOAuth2Service(oauth2Repository: OmaDataOAuth2Repository, val applic
.getOrElse(LocalizedString.unlocalized(clientId))
}

def findAktiivisetJaPäättyneetOpinnot(oppijaOid: String, scope: String, overrideSession: KoskiSpecificSession): Either[HttpStatus, OmaDataOAuth2AktiivisetJaPäättyneetOpiskeluoikeudet] = {
def findAktiivisetJaPäättyneetOpinnot(oppijaOid: String, scope: String, overrideSession: KoskiSpecificSession, tokenExpirationTime: String): Either[HttpStatus, OmaDataOAuth2AktiivisetJaPäättyneetOpiskeluoikeudet] = {
application.aktiivisetJaPäättyneetOpinnotService.findAktiivisetJaPäättyneetOpinnotOppija(
oppijaOid,
merkitseSuoritusjakoTehdyksi = false
)(overrideSession).map(oppija => {
OmaDataOAuth2AktiivisetJaPäättyneetOpiskeluoikeudet(
henkilö = OmaDataOAuth2Henkilötiedot(oppija.henkilö, scope),
opiskeluoikeudet = oppija.opiskeluoikeudet
opiskeluoikeudet = oppija.opiskeluoikeudet,
tokenInfo = OmaDataOAuth2TokenInfo(scope, tokenExpirationTime)
)
})
}

def findKaikkiTiedot(oppijaOid: String, scope: String, overrideSession: KoskiSpecificSession): Either[HttpStatus, OmaDataOAuth2KaikkiOpiskeluoikeudet] = {
def findKaikkiTiedot(oppijaOid: String, scope: String, overrideSession: KoskiSpecificSession, tokenExpirationTime: String): Either[HttpStatus, OmaDataOAuth2KaikkiOpiskeluoikeudet] = {
application.oppijaFacade.findOppija(oppijaOid)(overrideSession).flatMap(_.warningsToLeft) match {
case Right(Oppija(henkilö: TäydellisetHenkilötiedot, opiskeluoikeudet: Seq[Opiskeluoikeus])) =>
Right(OmaDataOAuth2KaikkiOpiskeluoikeudet(
henkilö = OmaDataOAuth2Henkilötiedot(henkilö, scope),
opiskeluoikeudet = opiskeluoikeudet.toList
opiskeluoikeudet = opiskeluoikeudet.toList,
tokenInfo = OmaDataOAuth2TokenInfo(scope, tokenExpirationTime)
))
case Right(_) =>
Left(KoskiErrorCategory.internalError("Datatype not recognized"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -974,7 +974,7 @@ class OmaDataOAuth2BackendSpec
Some(validRedirectUri),
scope.split(" ").toSet
).getOrElse(throw new Error("Internal error"))
.successResponse.access_token
.accessToken

postResourceServer(token, palveluKäyttäjä) {
verifyResponseStatus(404)
Expand Down

0 comments on commit 480498b

Please sign in to comment.