Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authentication #70

Merged
merged 10 commits into from
Oct 2, 2021
Merged
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
1 change: 0 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
implementation("javax.xml.bind:jaxb-api:2.3.1")
Expand Down
2 changes: 1 addition & 1 deletion examples/deployment/restart-classroom.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
cd $CLASSROOM_DIR
cd "$CLASSROOM_DIR" || exit
docker-compose pull classroom
docker-compose up -d --remove-orphans classroom
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.KotlinModule
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.codec.ServerCodecConfigurer
Expand Down Expand Up @@ -36,7 +37,10 @@ class SerializationConfig : WebFluxConfigurer {
*/
@Bean
fun objectMapper(): ObjectMapper {
return ObjectMapper().registerKotlinModule()
val om = ObjectMapper()
om.registerModule(JavaTimeModule())
om.registerModule(KotlinModule())
return om
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,34 @@
package de.thm.mni.ii.classroom.controller

import de.thm.mni.ii.classroom.model.classroom.User
import de.thm.mni.ii.classroom.security.exception.UnauthorizedException
import de.thm.mni.ii.classroom.security.jwt.ClassroomAuthentication
import de.thm.mni.ii.classroom.security.jwt.ClassroomJwtService
import de.thm.mni.ii.classroom.security.jwt.ClassroomTokenRepository
import de.thm.mni.ii.classroom.util.component1
import de.thm.mni.ii.classroom.util.component2
import org.apache.commons.lang3.RandomStringUtils
import org.slf4j.LoggerFactory
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.server.reactive.ServerHttpResponse
import org.springframework.web.bind.annotation.CrossOrigin
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono
import reactor.kotlin.core.publisher.switchIfEmpty

@RestController
@RequestMapping("/classroom-api")
@CrossOrigin
class ClassroomApiController {
class ClassroomApiController(
val classroomTokenRepository: ClassroomTokenRepository,
val jwtService: ClassroomJwtService
) {

private val logger = LoggerFactory.getLogger(this::class.java)

Expand All @@ -21,7 +38,59 @@ class ClassroomApiController {
* @return
*/
@GetMapping("/join")
fun joinClassroom(auth: ClassroomAuthentication) = Mono.empty<String>().doOnNext {
logger.info("${auth.principal.fullName} joined classroom ${auth.principal.classroomId}.")
@ResponseStatus(HttpStatus.NO_CONTENT)
fun joinClassroom(auth: ClassroomAuthentication, originalExchange: ServerWebExchange): Mono<ServerHttpResponse> {
return Mono.just(generateRefreshToken(auth.principal))
.map { refreshToken ->
// Set refresh_token header
val refreshTokenSet = setHeader("refresh_token", refreshToken, originalExchange)
// Set Authorization header
setHeader(HttpHeaders.AUTHORIZATION, "Bearer ${auth.credentials}", refreshTokenSet).response
}.doOnNext {
logger.info("${auth.principal} joined classroom ${auth.principal.classroomId}.")
}
}

@GetMapping("/refresh")
@ResponseStatus(HttpStatus.NO_CONTENT)
fun refreshToken(
auth: ClassroomAuthentication,
originalExchange: ServerWebExchange,
@RequestHeader("refresh_token") refreshToken: String
): Mono<ServerHttpResponse> {
return classroomTokenRepository
.findRefreshToken(refreshToken)
.switchIfEmpty {
val ex = UnauthorizedException("Invalid refresh token provided by user ${auth.user?.fullName}")
logger.error("", ex)
Mono.error(ex)
}
.filter { user ->
user == auth.user
}.switchIfEmpty {
val ex = UnauthorizedException("Owner of refresh token does not match requester!")
logger.error("", ex)
Mono.error(ex)
}.map { user ->
val newRefreshToken = generateRefreshToken(user)
Pair(user, setHeader("refresh_token", newRefreshToken, originalExchange))
}.flatMap { (user, exchange) ->
Mono.zip(jwtService.createToken(user), Mono.just(exchange))
}.map { (jwt, exchange) ->
setHeader(HttpHeaders.AUTHORIZATION, "Bearer $jwt", exchange).response
}.doOnNext {
logger.info("${auth.principal} refreshed his JWT!")
}
}

private fun generateRefreshToken(user: User): String {
val newRefreshToken = RandomStringUtils.randomAscii(30)
classroomTokenRepository.insertRefreshToken(newRefreshToken, user)
return newRefreshToken
}

private fun setHeader(header: String, value: String, exchange: ServerWebExchange): ServerWebExchange {
exchange.response.headers.add(header, value)
return exchange
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ import java.io.Serializable
JsonSubTypes.Type(value = ConferenceEvent::class, name = "ConferenceEvent"),
JsonSubTypes.Type(value = InvitationEvent::class, name = "InvitationEvent"),
)
abstract class ClassroomEvent(@field:SuppressWarnings("unused") private val eventName: String) : Serializable
abstract class ClassroomEvent(@field:Suppress("unused") private val eventName: String) : Serializable

data class MessageEvent(val message: String) : ClassroomEvent(MessageEvent::class.simpleName!!)
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import javax.xml.bind.annotation.XmlType
@XmlRootElement(name = "attendees")
class Attendees(users: Set<User>? = null) {

@Suppress("unused")
@XmlElement(name = "attendee")
private val attendees = users?.map(::Attendee)
}
Expand All @@ -26,6 +27,7 @@ class Attendees(users: Set<User>? = null) {
]
)

@Suppress("unused")
class Attendee(user: User? = null) {
@XmlElement private val userID: String? = user?.userId
@XmlElement private val fullName: String? = user?.fullName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import javax.xml.bind.annotation.XmlElement
import javax.xml.bind.annotation.XmlRootElement
import javax.xml.bind.annotation.XmlType

@Suppress("unused")
@XmlRootElement(name = "response")
@XmlType(
propOrder = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package de.thm.mni.ii.classroom.model.api
import javax.xml.bind.annotation.XmlRootElement
import javax.xml.bind.annotation.XmlType

@Suppress("unused")
@XmlRootElement(name = "response")
@XmlType(propOrder = ["returncode", "messageKey", "message"])
class EndMeetingBBBResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import javax.xml.bind.annotation.XmlElement
import javax.xml.bind.annotation.XmlRootElement
import javax.xml.bind.annotation.XmlType

@Suppress("unused")
@XmlRootElement(name = "response")
@XmlType(
propOrder = [
Expand All @@ -27,13 +28,15 @@ class GetMeetingsBBBResponse(
@XmlElement(required = true, nillable = true) private val meetings = Meetings(digitalClassrooms)
}

@Suppress("unused")
@XmlRootElement(name = "meetings")
class Meetings(digitalClassrooms: List<DigitalClassroom>? = null) {

@XmlElement(name = "meeting")
private val meetings = digitalClassrooms?.map(::Meeting)
}

@Suppress("unused")
@XmlType(
name = "meeting",
propOrder = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import javax.xml.bind.annotation.XmlElement
import javax.xml.bind.annotation.XmlRootElement
import javax.xml.bind.annotation.XmlType

@Suppress("unused")
@XmlRootElement(name = "response")
@XmlType(propOrder = ["returncode", "messageKey", "message", "meetingID", "userID", "authToken", "sessionToken", "url"])
class JoinRoomBBBResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import javax.xml.bind.annotation.XmlElement
import javax.xml.bind.annotation.XmlRootElement
import javax.xml.bind.annotation.XmlType

@Suppress("unused")
@XmlRootElement(name = "response")
@XmlType(
propOrder = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import javax.xml.bind.annotation.XmlRootElement
import javax.xml.bind.annotation.XmlTransient
import javax.xml.bind.annotation.XmlType

@Suppress("unused")
@XmlTransient
open class SuperMessageBBB(
success: Boolean,
@field:XmlElement val messageKey: String?,
@field:XmlElement val message: String?
) : ReturnCodeBBB(success)

@Suppress("unused")
@XmlRootElement(name = "response")
@XmlType(propOrder = ["returncode", "messageKey", "message"])
class MessageBBB(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import javax.xml.bind.annotation.XmlElement
import javax.xml.bind.annotation.XmlTransient
import javax.xml.bind.annotation.XmlType

@Suppress("unused")
@XmlTransient
@XmlType(propOrder = ["returncode"])
abstract class ReturnCodeBBB(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,6 @@ class ConferenceStorage {
return conferences[conferenceId]
}

fun getLatestConferenceOfUser(user: User): Conference? {
return getConferencesOfUser(user).lastOrNull()
}

fun leaveConference(user: User, conference: Conference): Conference {
this.conferences[conference.conferenceId]!!.attendees.remove(user)
this.usersConference[user]?.remove(conference)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,8 @@ open class User(
result = 31 * result + userId.hashCode()
return result
}

override fun toString(): String {
return "${this.userId} / ${this.fullName}"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ data class JwtProperties(
/**
* validity duration of JWT in seconds
*/
@NotBlank @Range(min = 60) val expiration: Int,
@NotBlank @Range(min = 60) val expiration: Long,

val jwtSubject: String = "classroom-auth"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.springframework.security.config.annotation.web.reactive.EnableWebFlux
import org.springframework.security.config.web.server.SecurityWebFiltersOrder
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.security.web.server.authentication.AuthenticationWebFilter
import reactor.core.publisher.Mono
import kotlin.text.Charsets.UTF_8

Expand All @@ -19,7 +20,6 @@ import kotlin.text.Charsets.UTF_8
class SecurityConfiguration(
private val classroomHttpSessionTokenSecurity: ClassroomHttpSessionTokenSecurity,
private val downstreamAPISecurity: DownstreamAPISecurity,
private val classroomHttpJwtSecurity: ClassroomHttpJwtSecurity
) {

/**
Expand All @@ -31,7 +31,10 @@ class SecurityConfiguration(
* @see ClassroomHttpJwtSecurity
*/
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
fun springSecurityFilterChain(
http: ServerHttpSecurity,
jwtFilter: AuthenticationWebFilter
): SecurityWebFilterChain {
http.exceptionHandling()
.authenticationEntryPoint { exchange, denied ->
val response = exchange.response
Expand All @@ -52,7 +55,7 @@ class SecurityConfiguration(
// Resolves authenticated requests to a User with a role STUDENT, TUTOR or TEACHER.
.addFilterAt(classroomHttpSessionTokenSecurity.sessionTokenFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
// As above, but with JWT
.addFilterAt(classroomHttpJwtSecurity.jwtFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
.addFilterAt(jwtFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.authorizeExchange() // Exchanges at the path below with Role GATEWAY is authorized.
.pathMatchers("/api/*")
.hasAuthority("GATEWAY")
Expand Down
Loading