From 3362f67df5886227d889c57ce07341031a06a3fe Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Thu, 3 Oct 2019 13:30:53 +0200 Subject: [PATCH 01/13] Explicit claim type --- src/main/kotlin/org/radarbase/auth/jersey/impl/JwtAuth.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/org/radarbase/auth/jersey/impl/JwtAuth.kt b/src/main/kotlin/org/radarbase/auth/jersey/impl/JwtAuth.kt index bbfc4f7..d83c328 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/impl/JwtAuth.kt +++ b/src/main/kotlin/org/radarbase/auth/jersey/impl/JwtAuth.kt @@ -43,5 +43,5 @@ class JwtAuth(project: String?, private val jwt: DecodedJWT) : Auth { override fun hasRole(projectId: String, role: String) = projectId == defaultProject - override fun getClaim(name: String) = jwt.getClaim(name).`as`(JsonNode::class.java) + override fun getClaim(name: String): JsonNode = jwt.getClaim(name).`as`(JsonNode::class.java) } From 688f7c3d730f9070f94bcab018aba88ba945033d Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Thu, 3 Oct 2019 14:02:20 +0200 Subject: [PATCH 02/13] Bump version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cdcbe1d..444b715 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { description = 'Library for authentication and authorization with the RADAR platform using Jersey' group = 'org.radarbase' -version = '0.1.0' +version = '0.1.1-SNAPSHOT' ext { githubRepoName = 'RADAR-base/ManagementPortal' From 91b5678b39da81f3ebe3a0a243ed463d04a54f3e Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Thu, 3 Oct 2019 14:02:30 +0200 Subject: [PATCH 03/13] Add some standard resource configs --- .../auth/jersey/ApplicationResourceConfig.kt | 10 +++++++++ .../auth/jersey/RadarResourceConfigFactory.kt | 22 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 src/main/kotlin/org/radarbase/auth/jersey/ApplicationResourceConfig.kt create mode 100644 src/main/kotlin/org/radarbase/auth/jersey/RadarResourceConfigFactory.kt diff --git a/src/main/kotlin/org/radarbase/auth/jersey/ApplicationResourceConfig.kt b/src/main/kotlin/org/radarbase/auth/jersey/ApplicationResourceConfig.kt new file mode 100644 index 0000000..cbe5844 --- /dev/null +++ b/src/main/kotlin/org/radarbase/auth/jersey/ApplicationResourceConfig.kt @@ -0,0 +1,10 @@ +package org.radarbase.auth.jersey + +import org.glassfish.jersey.internal.inject.AbstractBinder +import org.glassfish.jersey.server.ResourceConfig + +interface ApplicationResourceConfig { + fun createEnhancers(): List + fun configureResources(resources: ResourceConfig) + fun configureBinder(binder: AbstractBinder) = Unit +} \ No newline at end of file diff --git a/src/main/kotlin/org/radarbase/auth/jersey/RadarResourceConfigFactory.kt b/src/main/kotlin/org/radarbase/auth/jersey/RadarResourceConfigFactory.kt new file mode 100644 index 0000000..06be864 --- /dev/null +++ b/src/main/kotlin/org/radarbase/auth/jersey/RadarResourceConfigFactory.kt @@ -0,0 +1,22 @@ +package org.radarbase.auth.jersey + +import org.glassfish.jersey.internal.inject.AbstractBinder +import org.glassfish.jersey.server.ResourceConfig + +class RadarResourceConfigFactory { + fun resources(appConfig: ApplicationResourceConfig): ResourceConfig { + val enhancers = appConfig.createEnhancers() + val resources = ResourceConfig() + resources.property("jersey.config.server.wadl.disableWadl", true) + appConfig.configureResources(resources) + enhancers.forEach { resources.packages(*it.packages) } + + resources.register(object : AbstractBinder() { + override fun configure() { + appConfig.configureBinder(this) + enhancers.forEach { it.enhance(this) } + } + }) + return resources + } +} \ No newline at end of file From 76360359f10ec51ec199b671d5a33b8820a34ae5 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 7 Oct 2019 09:56:44 +0200 Subject: [PATCH 04/13] Add more functionality --- README.md | 59 +++---- build.gradle | 145 ++---------------- gradle/publishing.gradle | 126 +++++++++++++++ settings.gradle | 2 +- .../auth/jersey/impl/PermissionFilter.kt | 93 ----------- .../{auth/jersey => jersey/auth}/Auth.kt | 32 ++-- .../jersey => jersey/auth}/AuthConfig.kt | 2 +- .../jersey => jersey/auth}/AuthValidator.kt | 4 +- .../jersey => jersey/auth}/Authenticated.kt | 2 +- .../jersey => jersey/auth}/NeedsPermission.kt | 2 +- .../jersey => jersey/auth}/ProjectService.kt | 4 +- .../auth/filter}/AuthenticationFilter.kt | 31 ++-- .../auth/filter}/AuthorizationFeature.kt | 10 +- .../jersey/auth/filter/PermissionFilter.kt | 64 ++++++++ .../auth/filter}/RadarSecurityContext.kt | 4 +- .../impl => jersey/auth/jwt}/AuthFactory.kt | 12 +- .../auth/jwt}/EcdsaJwtTokenValidator.kt | 6 +- .../impl => jersey/auth/jwt}/JwtAuth.kt | 4 +- .../managementportal}/ManagementPortalAuth.kt | 7 +- .../ManagementPortalTokenValidator.kt | 7 +- .../TokenValidatorFactory.kt | 8 +- .../config}/ApplicationResourceConfig.kt | 2 +- .../config}/EcdsaResourceEnhancer.kt | 7 +- .../GeneralExceptionResourceEnhancer.kt | 13 ++ .../config/HttpExceptionResourceEnhancer.kt | 20 +++ .../config}/JerseyResourceEnhancer.kt | 8 +- .../ManagementPortalResourceEnhancer.kt | 9 +- .../config}/RadarJerseyResourceEnhancer.kt | 32 ++-- .../config}/RadarResourceConfigFactory.kt | 11 +- .../exception/HttpApplicationException.kt | 5 +- .../exception/HttpBadGatewayException.kt | 15 ++ .../exception/HttpBadRequestException.kt | 15 ++ .../jersey/exception/HttpConflictException.kt | 15 ++ .../exception/HttpForbiddenException.kt | 15 ++ .../exception/HttpGatewayTimeoutException.kt | 15 ++ .../exception/HttpInternalServerException.kt | 15 ++ .../exception/HttpInvalidContentException.kt | 13 ++ .../jersey/exception/HttpNotFoundException.kt | 15 ++ .../exception/HttpRequestEntityTooLarge.kt | 15 ++ .../exception/HttpUnauthorizedException.kt | 15 ++ .../mapper}/DefaultExceptionHtmlRenderer.kt | 4 +- .../mapper}/ExceptionHtmlRenderer.kt | 4 +- .../mapper}/HttpApplicationExceptionMapper.kt | 19 +-- .../mapper/UnhandledExceptionMapper.kt | 31 ++++ .../mapper/WebApplicationExceptionMapper.kt | 28 ++++ .../exception/mapper}/4xx.html | 0 .../exception/mapper}/5xx.html | 0 .../DefaultExceptionHtmlRendererTest.kt | 40 ----- .../jersey => jersey/auth}/OAuthHelper.kt | 12 +- .../auth}/RadarJerseyResourceEnhancerTest.kt | 7 +- .../jersey/mock/MockProjectService.kt | 2 +- .../jersey/mock/resource/MockResource.kt | 2 +- 52 files changed, 599 insertions(+), 429 deletions(-) create mode 100644 gradle/publishing.gradle delete mode 100644 src/main/kotlin/org/radarbase/auth/jersey/impl/PermissionFilter.kt rename src/main/kotlin/org/radarbase/{auth/jersey => jersey/auth}/Auth.kt (56%) rename src/main/kotlin/org/radarbase/{auth/jersey => jersey/auth}/AuthConfig.kt (94%) rename src/main/kotlin/org/radarbase/{auth/jersey => jersey/auth}/AuthValidator.kt (91%) rename src/main/kotlin/org/radarbase/{auth/jersey => jersey/auth}/Authenticated.kt (94%) rename src/main/kotlin/org/radarbase/{auth/jersey => jersey/auth}/NeedsPermission.kt (96%) rename src/main/kotlin/org/radarbase/{auth/jersey => jersey/auth}/ProjectService.kt (83%) rename src/main/kotlin/org/radarbase/{auth/jersey/inject => jersey/auth/filter}/AuthenticationFilter.kt (71%) rename src/main/kotlin/org/radarbase/{auth/jersey/inject => jersey/auth/filter}/AuthorizationFeature.kt (74%) create mode 100644 src/main/kotlin/org/radarbase/jersey/auth/filter/PermissionFilter.kt rename src/main/kotlin/org/radarbase/{auth/jersey/impl => jersey/auth/filter}/RadarSecurityContext.kt (94%) rename src/main/kotlin/org/radarbase/{auth/jersey/impl => jersey/auth/jwt}/AuthFactory.kt (70%) rename src/main/kotlin/org/radarbase/{auth/jersey => jersey/auth/jwt}/EcdsaJwtTokenValidator.kt (96%) rename src/main/kotlin/org/radarbase/{auth/jersey/impl => jersey/auth/jwt}/JwtAuth.kt (96%) rename src/main/kotlin/org/radarbase/{auth/jersey/impl => jersey/auth/managementportal}/ManagementPortalAuth.kt (82%) rename src/main/kotlin/org/radarbase/{auth/jersey => jersey/auth/managementportal}/ManagementPortalTokenValidator.kt (89%) rename src/main/kotlin/org/radarbase/{auth/jersey/impl => jersey/auth/managementportal}/TokenValidatorFactory.kt (79%) rename src/main/kotlin/org/radarbase/{auth/jersey => jersey/config}/ApplicationResourceConfig.kt (89%) rename src/main/kotlin/org/radarbase/{auth/jersey => jersey/config}/EcdsaResourceEnhancer.kt (81%) create mode 100644 src/main/kotlin/org/radarbase/jersey/config/GeneralExceptionResourceEnhancer.kt create mode 100644 src/main/kotlin/org/radarbase/jersey/config/HttpExceptionResourceEnhancer.kt rename src/main/kotlin/org/radarbase/{auth/jersey => jersey/config}/JerseyResourceEnhancer.kt (62%) rename src/main/kotlin/org/radarbase/{auth/jersey => jersey/config}/ManagementPortalResourceEnhancer.kt (76%) rename src/main/kotlin/org/radarbase/{auth/jersey => jersey/config}/RadarJerseyResourceEnhancer.kt (54%) rename src/main/kotlin/org/radarbase/{auth/jersey => jersey/config}/RadarResourceConfigFactory.kt (52%) rename src/main/kotlin/org/radarbase/{auth => }/jersey/exception/HttpApplicationException.kt (83%) create mode 100644 src/main/kotlin/org/radarbase/jersey/exception/HttpBadGatewayException.kt create mode 100644 src/main/kotlin/org/radarbase/jersey/exception/HttpBadRequestException.kt create mode 100644 src/main/kotlin/org/radarbase/jersey/exception/HttpConflictException.kt create mode 100644 src/main/kotlin/org/radarbase/jersey/exception/HttpForbiddenException.kt create mode 100644 src/main/kotlin/org/radarbase/jersey/exception/HttpGatewayTimeoutException.kt create mode 100644 src/main/kotlin/org/radarbase/jersey/exception/HttpInternalServerException.kt create mode 100644 src/main/kotlin/org/radarbase/jersey/exception/HttpInvalidContentException.kt create mode 100644 src/main/kotlin/org/radarbase/jersey/exception/HttpNotFoundException.kt create mode 100644 src/main/kotlin/org/radarbase/jersey/exception/HttpRequestEntityTooLarge.kt create mode 100644 src/main/kotlin/org/radarbase/jersey/exception/HttpUnauthorizedException.kt rename src/main/kotlin/org/radarbase/{auth/jersey/exception => jersey/exception/mapper}/DefaultExceptionHtmlRenderer.kt (95%) rename src/main/kotlin/org/radarbase/{auth/jersey/exception => jersey/exception/mapper}/ExceptionHtmlRenderer.kt (75%) rename src/main/kotlin/org/radarbase/{auth/jersey/inject => jersey/exception/mapper}/HttpApplicationExceptionMapper.kt (84%) create mode 100644 src/main/kotlin/org/radarbase/jersey/exception/mapper/UnhandledExceptionMapper.kt create mode 100644 src/main/kotlin/org/radarbase/jersey/exception/mapper/WebApplicationExceptionMapper.kt rename src/main/resources/org/radarbase/{auth/jersey/exception => jersey/exception/mapper}/4xx.html (100%) rename src/main/resources/org/radarbase/{auth/jersey/exception => jersey/exception/mapper}/5xx.html (100%) delete mode 100644 src/test/kotlin/org/radarbase/auth/jersey/exception/DefaultExceptionHtmlRendererTest.kt rename src/test/kotlin/org/radarbase/{auth/jersey => jersey/auth}/OAuthHelper.kt (89%) rename src/test/kotlin/org/radarbase/{auth/jersey => jersey/auth}/RadarJerseyResourceEnhancerTest.kt (96%) rename src/test/kotlin/org/radarbase/{auth => }/jersey/mock/MockProjectService.kt (94%) rename src/test/kotlin/org/radarbase/{auth => }/jersey/mock/resource/MockResource.kt (96%) diff --git a/README.md b/README.md index 5b41a93..26a1464 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# radar-auth-jersey +# radar-jersey -Library to facilitate OAuth 2.0 integration with a Jersey-based REST API. +Library to facilitate using with a Jersey-based REST API. This includes OAuth 2.0 integration, exception handling and resource configuration. # Usage @@ -11,17 +11,18 @@ repositories { } dependencies { - api("org.radarbase:radar-auth-jersey:0.1.0") + api("org.radarbase:radar-jersey:0.2.0") } ``` -Any path or resource that should be authenticated against the ManagementPortal, should be annotated with `@Authenticated`. Specific authorization can be checked by adding a `@NeedsPermission` annotation. An `Auth` object can be injected to get app-specific information. Examples: +Any path or resource that should be authenticated against the ManagementPortal, should be annotated with `@Authenticated`. Specific authorization can be checked by adding a `@NeedsPermission` annotation. An `Auth` object can be injected to get app-specific information. For reliable injection, constructor or method injection, not class parameter injection. Examples: ```kotlin @Path("/projects") @Authenticated -class Users(@Context projectService: ProjectService) { - +class Users( + @Context projectService: MyProjectService +) { @GET @NeedsPermission(PROJECT, READ) fun getProjects(@Context auth: Auth): List { @@ -47,32 +48,36 @@ class Users(@Context projectService: ProjectService) { These APIs are activated by adding `JerseyResourceEnhancer` implementations to your resource definition: ```kotlin -val authConfig = AuthConfig( - managementPortalUrl = "http://...", - jwtResourceName = "res_MyResource") - -val enhancers = listOf( - RadarJerseyResourceEnhancer(authConfig) - ManagementPortalResourceEnhancer()) - -val resourceConfig = ResourceConfig() -enhancers.forEach { resourceConfig.packages(*it.packages) } - -resourceConfig.register(object : AbstractBinder() { - override fun configure() { - bind(MyProjectService::class.java) - .to(ProjectService::class.java) - .`in`(Singleton::class.java) - - enhancers.forEach { it.enhance(this) } +val resourceConfig = RadarResourceConfigFactory().resources(listOf( + // My own resource configuration + object : JerseyResourceEnhancer { + override fun enhanceBinder(binder: AbstractBinder) { + binder.bind(MyProjectService::class.java) + .to(ProjectService::class.java) + .`in`(Singleton::class.java) + + binder.bind(MyProjectService::class.java) + .to(MyProjectService::class.java) + .`in`(Singleton::class.java) + } } -}) + // RADAR OAuth2 enhancement + RadarJerseyResourceEnhancer(AuthConfig( + managementPortalUrl = "http://...", + jwtResourceName = "res_MyResource")), + // Use ManagementPortal OAuth implementation + ManagementPortalResourceEnhancer(), + // HttpApplicationException handling + HttpExceptionResourceEnhancer(), + // General error handling (WebApplicationException and any other Exception) + GeneralExceptionResourceEnhancer() +)) ``` -Ensure that a class implementing `org.radarbase.auth.jersey.ProjectService` is added to the binder. +Ensure that a class implementing `org.radarbase.jersey.auth.ProjectService` is added to the binder. ## Error handling This package adds some error handling. Specifically, `org.radarbase.auth.jersey.exception.HttpApplicationException` can be used and extended to serve detailed error messages with customized logging and HTML templating. They can be thrown from any resource. -To serve custom HTML error messages for error codes 400 to 599, add a Mustache template to the classpath in directory `org/radarbase/auth/jersey/exception/.html`. You can use special cases `4xx.html` and `5xx.html` as a catch-all template. The templates can use variables `status` for the HTTP status code, `code` for short-hand code for the specific error, and an optional `detailedMessage` for a human-readable message. +To serve custom HTML error messages for error codes 400 to 599, add a Mustache template to the classpath in directory `org/radarbase/jersey/exception/mapper/.html`. You can use special cases `4xx.html` and `5xx.html` as a catch-all template. The templates can use variables `status` for the HTTP status code, `code` for short-hand code for the specific error, and an optional `detailedMessage` for a human-readable message. diff --git a/build.gradle b/build.gradle index 444b715..1532484 100644 --- a/build.gradle +++ b/build.gradle @@ -1,16 +1,15 @@ plugins { - id 'org.jetbrains.kotlin.jvm' version '1.3.41' - id 'com.jfrog.bintray' version '1.8.4' - id 'maven-publish' + id 'org.jetbrains.kotlin.jvm' version '1.3.50' + id 'com.jfrog.bintray' version '1.8.4' apply false } -description = 'Library for authentication and authorization with the RADAR platform using Jersey' +description = 'Library for Jersey authorization, exception handling and configuration with the RADAR platform' group = 'org.radarbase' version = '0.1.1-SNAPSHOT' ext { - githubRepoName = 'RADAR-base/ManagementPortal' - githubUrl = 'https://github.com/RADAR-base/ManagementPortal' + githubRepoName = 'RADAR-base/radar-jersey' + githubUrl = "https://github.com/${githubRepoName}.git".toString() issueUrl = "https://github.com/$githubRepoName/issues".toString() website = 'http://radar-base.org' @@ -40,7 +39,9 @@ dependencies { api("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion") + // exception template rendering implementation 'com.github.spullara.mustache.java:compiler:0.9.6' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "org.slf4j:slf4j-api:1.7.26" @@ -67,10 +68,6 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions.jvmTarget = "11" } -repositories { - mavenCentral() -} - test { useJUnitPlatform() testLogging { @@ -78,130 +75,8 @@ test { } } -def sharedManifest = manifest { - attributes("Implementation-Title": project.name, - "Implementation-Version": version) -} - -jar { -// archiveBaseName.set(project.name) - manifest.from sharedManifest -} - -// custom tasks for creating source/javadoc jars -task sourcesJar(type: Jar, dependsOn: classes) { - archiveClassifier.set('sources') - from sourceSets.main.allSource - manifest.from sharedManifest -} - -task javadocJar(type: Jar, dependsOn: javadoc) { - archiveClassifier.set('javadoc') - from javadoc.destinationDir - manifest.from sharedManifest -} - -// add javadoc/source jar tasks as artifacts -artifacts { - archives sourcesJar, javadocJar -} - -ext.nexusRepoBase = 'https://repo.thehyve.nl/content/repositories' - -publishing { - publications { - mavenJar(MavenPublication) { - from components.java - artifact sourcesJar - artifact javadocJar - - pom { - name = project.name - description = project.description - url = githubUrl - licenses { - license { - name = 'The Apache Software License, Version 2.0' - url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' - distribution = 'repo' - } - } - developers { - developer { - id = 'dennyverbeeck' - name = 'Denny Verbeeck' - email = 'dverbeec@its.jnj.com' - organization = 'Janssen R&D' - } - developer { - id = 'blootsvoets' - name = 'Joris Borgdorff' - email = 'joris@thehyve.nl' - organization = 'The Hyve' - } - developer { - id = 'nivemaham' - name = 'Nivethika Mahasivam' - email = 'nivethika@thehyve.nl' - organization = 'The Hyve' - } - } - issueManagement { - system = 'GitHub' - url = githubUrl + '/issues' - } - organization { - name = 'RADAR-base' - url = website - } - scm { - connection = 'scm:git:' + githubUrl - url = githubUrl - } - } - - } - } - repositories { - maven { - def releasesRepoUrl = "$nexusRepoBase/releases" - def snapshotsRepoUrl = "$nexusRepoBase/snapshots" - url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl - credentials { - username = project.hasProperty('nexusUser') ? project.property('nexusUser') : System.getenv('NEXUS_USER') - password = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : System.getenv('NEXUS_PASSWORD') - } - } - } -} - -bintray { - user project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER') - key project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY') - override false - publications 'mavenJar' - pkg { - repo = project.group - name = project.name - userOrg = 'radar-base' - desc = project.description - licenses = ['Apache-2.0'] - websiteUrl = website - issueTrackerUrl = issueUrl - vcsUrl = githubUrl - githubRepo = githubRepoName - githubReleaseNotesFile = 'README.md' - version { - name = project.version - desc = project.description - vcsTag = System.getenv('TRAVIS_TAG') - released = new Date() - } - } -} - -bintrayUpload.dependsOn 'assemble' - wrapper { gradleVersion = "5.6.2" -} \ No newline at end of file +} + +apply from: "gradle/publishing.gradle" \ No newline at end of file diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle new file mode 100644 index 0000000..8421f1b --- /dev/null +++ b/gradle/publishing.gradle @@ -0,0 +1,126 @@ +apply plugin: 'maven-publish' +apply plugin: 'com.jfrog.bintray' + +def sharedManifest = manifest { + attributes("Implementation-Title": project.name, + "Implementation-Version": version) +} + +jar { +// archiveBaseName.set(project.name) + manifest.from sharedManifest +} + +// custom tasks for creating source/javadoc jars +task sourcesJar(type: Jar, dependsOn: classes) { + archiveClassifier.set('sources') + from sourceSets.main.allSource + manifest.from sharedManifest +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + archiveClassifier.set('javadoc') + from javadoc.destinationDir + manifest.from sharedManifest +} + +// add javadoc/source jar tasks as artifacts +artifacts { + archives sourcesJar, javadocJar +} + +ext.nexusRepoBase = 'https://repo.thehyve.nl/content/repositories' + +publishing { + publications { + mavenJar(MavenPublication) { + from components.java + artifact sourcesJar + artifact javadocJar + + pom { + name = project.name + description = project.description + url = githubUrl + licenses { + license { + name = 'The Apache Software License, Version 2.0' + url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' + distribution = 'repo' + } + } + developers { + developer { + id = 'dennyverbeeck' + name = 'Denny Verbeeck' + email = 'dverbeec@its.jnj.com' + organization = 'Janssen R&D' + } + developer { + id = 'blootsvoets' + name = 'Joris Borgdorff' + email = 'joris@thehyve.nl' + organization = 'The Hyve' + } + developer { + id = 'nivemaham' + name = 'Nivethika Mahasivam' + email = 'nivethika@thehyve.nl' + organization = 'The Hyve' + } + } + issueManagement { + system = 'GitHub' + url = githubUrl + '/issues' + } + organization { + name = 'RADAR-base' + url = website + } + scm { + connection = 'scm:git:' + githubUrl + url = githubUrl + } + } + + } + } + repositories { + maven { + def releasesRepoUrl = "$nexusRepoBase/releases" + def snapshotsRepoUrl = "$nexusRepoBase/snapshots" + url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl + credentials { + username = project.hasProperty('nexusUser') ? project.property('nexusUser') : System.getenv('NEXUS_USER') + password = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : System.getenv('NEXUS_PASSWORD') + } + } + } +} + +bintray { + user project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER') + key project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY') + override false + publications 'mavenJar' + pkg { + repo = project.group + name = project.name + userOrg = 'radar-base' + desc = project.description + licenses = ['Apache-2.0'] + websiteUrl = website + issueTrackerUrl = issueUrl + vcsUrl = githubUrl + githubRepo = githubRepoName + githubReleaseNotesFile = 'README.md' + version { + name = project.version + desc = project.description + vcsTag = System.getenv('TRAVIS_TAG') + released = new Date() + } + } +} + +bintrayUpload.dependsOn 'assemble' diff --git a/settings.gradle b/settings.gradle index 14d78d9..000275e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = "radar-auth-jersey" +rootProject.name = "radar-jersey" \ No newline at end of file diff --git a/src/main/kotlin/org/radarbase/auth/jersey/impl/PermissionFilter.kt b/src/main/kotlin/org/radarbase/auth/jersey/impl/PermissionFilter.kt deleted file mode 100644 index 37b7c92..0000000 --- a/src/main/kotlin/org/radarbase/auth/jersey/impl/PermissionFilter.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2019. The Hyve - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * - * See the file LICENSE in the root of this repository. - */ - -package org.radarbase.auth.jersey.impl - -import org.radarbase.auth.jersey.* -import org.radarbase.auth.jersey.inject.AuthenticationFilter -import org.radarcns.auth.authorization.Permission -import org.slf4j.LoggerFactory -import java.lang.IllegalArgumentException -import javax.ws.rs.container.ContainerRequestContext -import javax.ws.rs.container.ContainerRequestFilter -import javax.ws.rs.container.ResourceInfo -import javax.ws.rs.core.Context -import javax.ws.rs.core.Response -import javax.ws.rs.core.UriInfo - -/** - * Check that the token has given permissions. - */ -class PermissionFilter : ContainerRequestFilter { - - @Context - private lateinit var resourceInfo: ResourceInfo - - @Context - private lateinit var auth: Auth - - @Context - private lateinit var projectService: ProjectService - - @Context - private lateinit var uriInfo: UriInfo - - override fun filter(requestContext: ContainerRequestContext) { - val resourceMethod = resourceInfo.resourceMethod - - val annotation = resourceMethod.getAnnotation(NeedsPermission::class.java) - - val permission = Permission(annotation.entity, annotation.operation) - - val projectId = annotation.projectPathParam - .takeIf { it.isNotEmpty() } - ?.let { uriInfo.pathParameters[it] } - ?.firstOrNull() - val userId = annotation.userPathParam - .takeIf { it.isNotEmpty() } - ?.let { uriInfo.pathParameters[it] } - ?.firstOrNull() - - val isAuthenticated = when { - userId != null -> projectId != null && auth.token.hasPermissionOnSubject(permission, projectId, userId) - projectId != null -> auth.token.hasPermissionOnProject(permission, projectId) - else -> auth.token.hasPermission(permission) - } - - if (!isAuthenticated) { - abortWithForbidden(requestContext, permission) - return - } - projectId?.let { projectService.ensureProject(it) } - } - - companion object { - private val logger = LoggerFactory.getLogger(PermissionFilter::class.java) - - /** - * Abort the request with a forbidden status. The caller must ensure that no other changes are - * made to the context (i.e., make a quick return). - * @param requestContext context to abort - * @param scope the permission that is needed. - */ - fun abortWithForbidden(requestContext: ContainerRequestContext, scope: Permission) { - val message = "$scope permission not given." - logger.warn("[403] {}: {}", - requestContext.uriInfo.path, message) - - requestContext.abortWith( - Response.status(Response.Status.FORBIDDEN) - .header("WWW-Authenticate", AuthenticationFilter.BEARER_REALM - + " error=\"insufficient_scope\"" - + " error_description=\"$message\"" - + " scope=\"$scope\"") - .build()) - } - } -} diff --git a/src/main/kotlin/org/radarbase/auth/jersey/Auth.kt b/src/main/kotlin/org/radarbase/jersey/auth/Auth.kt similarity index 56% rename from src/main/kotlin/org/radarbase/auth/jersey/Auth.kt rename to src/main/kotlin/org/radarbase/jersey/auth/Auth.kt index b82fe40..731b648 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/Auth.kt +++ b/src/main/kotlin/org/radarbase/jersey/auth/Auth.kt @@ -7,10 +7,11 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey +package org.radarbase.jersey.auth import com.fasterxml.jackson.databind.JsonNode -import org.radarbase.auth.jersey.exception.HttpApplicationException +import org.radarbase.jersey.exception.HttpBadRequestException +import org.radarbase.jersey.exception.HttpForbiddenException import org.radarcns.auth.authorization.Permission import org.radarcns.auth.token.RadarToken import javax.ws.rs.core.Response @@ -32,14 +33,15 @@ interface Auth { /** * Check whether the current authentication has given permissions on a subject in a project. * - * @throws HttpApplicationException if the current authentication does not authorize for the permission. + * @throws HttpBadRequestException if a parameter is null + * @throws HttpForbiddenException if the current authentication does not authorize for the permission. */ fun checkPermissionOnSubject(permission: Permission, projectId: String?, userId: String?) { if (!token.hasPermissionOnSubject(permission, - projectId ?: throw HttpApplicationException(Response.Status.BAD_REQUEST, "project_id_missing", "Missing project ID in request"), - userId ?: throw HttpApplicationException(Response.Status.BAD_REQUEST, "user_id_missing", "Missing user ID in request") + projectId ?: throw HttpBadRequestException("project_id_missing", "Missing project ID in request"), + userId ?: throw HttpBadRequestException("user_id_missing", "Missing user ID in request") )) { - throw HttpApplicationException(Response.Status.FORBIDDEN, "permission_mismatch", "No permission to create measurement for " + + throw HttpForbiddenException("permission_mismatch", "No permission to create measurement for " + "project $projectId with user $userId") } } @@ -47,13 +49,14 @@ interface Auth { /** * Check whether the current authentication has given permissions on a project. * - * @throws HttpApplicationException if the current authentication does not authorize for the permission. + * @throws HttpBadRequestException if a parameter is null + * @throws HttpForbiddenException if the current authentication does not authorize for the permission. */ fun checkPermissionOnProject(permission: Permission, projectId: String?) { if (!token.hasPermissionOnProject(permission, - projectId ?: throw HttpApplicationException(Response.Status.BAD_REQUEST, "project_id_missing", "Missing project ID in request") + projectId ?: throw HttpBadRequestException("project_id_missing", "Missing project ID in request") )) { - throw HttpApplicationException(Response.Status.FORBIDDEN, "permission_mismatch", "No permission to create measurement for " + + throw HttpForbiddenException("permission_mismatch", "No permission to create measurement for " + "project $projectId") } } @@ -61,14 +64,15 @@ interface Auth { /** * Check whether the current authentication has given permissions. * - * @throws HttpApplicationException if the current authentication does not authorize for the permission. + * @throws HttpBadRequestException if a parameter is null + * @throws HttpForbiddenException if the current authentication does not authorize for the permission. */ fun checkPermissionOnSource(permission: Permission, projectId: String?, userId: String?, sourceId: String?) { if (!token.hasPermissionOnSource(permission, - projectId ?: throw HttpApplicationException(Response.Status.BAD_REQUEST, "project_id_missing", "Missing project ID in request"), - userId ?: throw HttpApplicationException(Response.Status.BAD_REQUEST, "user_id_missing", "Missing user ID in request"), - sourceId ?: throw HttpApplicationException(Response.Status.BAD_REQUEST, "source_id_missing", "Missing source ID in request"))) { - throw HttpApplicationException(Response.Status.FORBIDDEN, "permission_mismatch", "No permission to create measurement for " + + projectId ?: throw HttpBadRequestException("project_id_missing", "Missing project ID in request"), + userId ?: throw HttpBadRequestException("user_id_missing", "Missing user ID in request"), + sourceId ?: throw HttpBadRequestException("source_id_missing", "Missing source ID in request"))) { + throw HttpForbiddenException("permission_mismatch", "No permission to create measurement for " + "project $projectId with user $userId and source $sourceId") } } diff --git a/src/main/kotlin/org/radarbase/auth/jersey/AuthConfig.kt b/src/main/kotlin/org/radarbase/jersey/auth/AuthConfig.kt similarity index 94% rename from src/main/kotlin/org/radarbase/auth/jersey/AuthConfig.kt rename to src/main/kotlin/org/radarbase/jersey/auth/AuthConfig.kt index 934d2fd..69b9765 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/AuthConfig.kt +++ b/src/main/kotlin/org/radarbase/jersey/auth/AuthConfig.kt @@ -7,7 +7,7 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey +package org.radarbase.jersey.auth data class AuthConfig( val managementPortalUrl: String? = null, diff --git a/src/main/kotlin/org/radarbase/auth/jersey/AuthValidator.kt b/src/main/kotlin/org/radarbase/jersey/auth/AuthValidator.kt similarity index 91% rename from src/main/kotlin/org/radarbase/auth/jersey/AuthValidator.kt rename to src/main/kotlin/org/radarbase/jersey/auth/AuthValidator.kt index ee9cc02..1d288ca 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/AuthValidator.kt +++ b/src/main/kotlin/org/radarbase/jersey/auth/AuthValidator.kt @@ -7,9 +7,9 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey +package org.radarbase.jersey.auth -import org.radarbase.auth.jersey.inject.AuthenticationFilter +import org.radarbase.jersey.auth.filter.AuthenticationFilter import org.radarcns.auth.exception.TokenValidationException import javax.ws.rs.container.ContainerRequestContext diff --git a/src/main/kotlin/org/radarbase/auth/jersey/Authenticated.kt b/src/main/kotlin/org/radarbase/jersey/auth/Authenticated.kt similarity index 94% rename from src/main/kotlin/org/radarbase/auth/jersey/Authenticated.kt rename to src/main/kotlin/org/radarbase/jersey/auth/Authenticated.kt index 90cbf79..c10f1cf 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/Authenticated.kt +++ b/src/main/kotlin/org/radarbase/jersey/auth/Authenticated.kt @@ -7,7 +7,7 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey +package org.radarbase.jersey.auth import javax.ws.rs.NameBinding diff --git a/src/main/kotlin/org/radarbase/auth/jersey/NeedsPermission.kt b/src/main/kotlin/org/radarbase/jersey/auth/NeedsPermission.kt similarity index 96% rename from src/main/kotlin/org/radarbase/auth/jersey/NeedsPermission.kt rename to src/main/kotlin/org/radarbase/jersey/auth/NeedsPermission.kt index ca3d682..574e24d 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/NeedsPermission.kt +++ b/src/main/kotlin/org/radarbase/jersey/auth/NeedsPermission.kt @@ -7,7 +7,7 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey +package org.radarbase.jersey.auth import org.radarcns.auth.authorization.Permission diff --git a/src/main/kotlin/org/radarbase/auth/jersey/ProjectService.kt b/src/main/kotlin/org/radarbase/jersey/auth/ProjectService.kt similarity index 83% rename from src/main/kotlin/org/radarbase/auth/jersey/ProjectService.kt rename to src/main/kotlin/org/radarbase/jersey/auth/ProjectService.kt index a6b49a3..15df479 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/ProjectService.kt +++ b/src/main/kotlin/org/radarbase/jersey/auth/ProjectService.kt @@ -7,9 +7,9 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey +package org.radarbase.jersey.auth -import org.radarbase.auth.jersey.exception.HttpApplicationException +import org.radarbase.jersey.exception.HttpApplicationException /** * Service to keep track of active projects. diff --git a/src/main/kotlin/org/radarbase/auth/jersey/inject/AuthenticationFilter.kt b/src/main/kotlin/org/radarbase/jersey/auth/filter/AuthenticationFilter.kt similarity index 71% rename from src/main/kotlin/org/radarbase/auth/jersey/inject/AuthenticationFilter.kt rename to src/main/kotlin/org/radarbase/jersey/auth/filter/AuthenticationFilter.kt index b6bfae9..3712977 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/inject/AuthenticationFilter.kt +++ b/src/main/kotlin/org/radarbase/jersey/auth/filter/AuthenticationFilter.kt @@ -7,13 +7,11 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey.inject +package org.radarbase.jersey.auth.filter -import org.radarbase.auth.jersey.AuthValidator -import org.radarbase.auth.jersey.Authenticated -import org.radarbase.auth.jersey.exception.HttpApplicationException -import org.radarbase.auth.jersey.impl.RadarSecurityContext -import org.radarcns.auth.authorization.Permission +import org.radarbase.jersey.auth.AuthValidator +import org.radarbase.jersey.auth.Authenticated +import org.radarbase.jersey.exception.HttpUnauthorizedException import org.radarcns.auth.exception.TokenValidationException import org.slf4j.LoggerFactory import javax.annotation.Priority @@ -21,7 +19,6 @@ import javax.ws.rs.Priorities import javax.ws.rs.container.ContainerRequestContext import javax.ws.rs.container.ContainerRequestFilter import javax.ws.rs.core.Context -import javax.ws.rs.core.Response import javax.ws.rs.ext.Provider /** @@ -30,9 +27,9 @@ import javax.ws.rs.ext.Provider @Provider @Authenticated @Priority(Priorities.AUTHENTICATION) -class AuthenticationFilter : ContainerRequestFilter { - @Context - private lateinit var validator: AuthValidator +class AuthenticationFilter( + @Context private val validator: AuthValidator +) : ContainerRequestFilter { override fun filter(requestContext: ContainerRequestContext) { val rawToken = validator.getToken(requestContext) @@ -40,8 +37,7 @@ class AuthenticationFilter : ContainerRequestFilter { if (rawToken == null) { logger.warn("[401] {}: No token bearer header provided in the request", requestContext.uriInfo.path) - throw HttpApplicationException( - status = Response.Status.UNAUTHORIZED, + throw HttpUnauthorizedException( code = "token_missing", detailedMessage = "No bearer token is provided in the request.", additionalHeaders = listOf("WWW-Authenticate" to BEARER_REALM)) @@ -51,8 +47,7 @@ class AuthenticationFilter : ContainerRequestFilter { validator.verify(rawToken, requestContext) } catch (ex: TokenValidationException) { logger.warn("[401] {}: {}", requestContext.uriInfo.path, ex.toString()) - throw HttpApplicationException( - status = Response.Status.UNAUTHORIZED, + throw HttpUnauthorizedException( code = "token_unverified", detailedMessage = "Cannot verify token. It may have been rendered invalid.", additionalHeaders = listOf("WWW-Authenticate" to BEARER_REALM @@ -63,8 +58,7 @@ class AuthenticationFilter : ContainerRequestFilter { if (radarToken == null) { logger.warn("[401] {}: Bearer token invalid", requestContext.uriInfo.path) - throw HttpApplicationException( - status = Response.Status.UNAUTHORIZED, + throw HttpUnauthorizedException( code = "token_invalid", detailedMessage = "Bearer token is not a valid JWT.", additionalHeaders = listOf("WWW-Authenticate" to BEARER_REALM)) @@ -78,10 +72,5 @@ class AuthenticationFilter : ContainerRequestFilter { const val BEARER_REALM: String = "Bearer realm=\"Kafka REST Proxy\"" const val BEARER = "Bearer " - - fun getInvalidScopeChallenge(message: String) = BEARER_REALM + - " error=\"insufficient_scope\"" + - " error_description=\"$message\"" + - " scope=\"${Permission.MEASUREMENT_CREATE.scopeName()}\"" } } diff --git a/src/main/kotlin/org/radarbase/auth/jersey/inject/AuthorizationFeature.kt b/src/main/kotlin/org/radarbase/jersey/auth/filter/AuthorizationFeature.kt similarity index 74% rename from src/main/kotlin/org/radarbase/auth/jersey/inject/AuthorizationFeature.kt rename to src/main/kotlin/org/radarbase/jersey/auth/filter/AuthorizationFeature.kt index 679f7e2..e59930e 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/inject/AuthorizationFeature.kt +++ b/src/main/kotlin/org/radarbase/jersey/auth/filter/AuthorizationFeature.kt @@ -7,11 +7,9 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey.inject +package org.radarbase.jersey.auth.filter -import org.radarbase.auth.jersey.NeedsPermission -import org.radarbase.auth.jersey.impl.PermissionFilter -import org.slf4j.LoggerFactory +import org.radarbase.jersey.auth.NeedsPermission import javax.ws.rs.Priorities import javax.ws.rs.container.DynamicFeature import javax.ws.rs.container.ResourceInfo @@ -27,8 +25,4 @@ class AuthorizationFeature : DynamicFeature { context.register(PermissionFilter::class.java, Priorities.AUTHORIZATION) } } - - companion object { - private val logger = LoggerFactory.getLogger(AuthorizationFeature::class.java) - } } diff --git a/src/main/kotlin/org/radarbase/jersey/auth/filter/PermissionFilter.kt b/src/main/kotlin/org/radarbase/jersey/auth/filter/PermissionFilter.kt new file mode 100644 index 0000000..5e9f50a --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/auth/filter/PermissionFilter.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019. The Hyve + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * See the file LICENSE in the root of this repository. + */ + +package org.radarbase.jersey.auth.filter + +import org.radarbase.jersey.auth.Auth +import org.radarbase.jersey.auth.NeedsPermission +import org.radarbase.jersey.auth.ProjectService +import org.radarbase.jersey.exception.HttpForbiddenException +import org.radarcns.auth.authorization.Permission +import javax.ws.rs.container.ContainerRequestContext +import javax.ws.rs.container.ContainerRequestFilter +import javax.ws.rs.container.ResourceInfo +import javax.ws.rs.core.Context +import javax.ws.rs.core.UriInfo + +/** + * Check that the token has given permissions. + */ +class PermissionFilter( + @Context private val resourceInfo: ResourceInfo, + @Context private val auth: Auth, + @Context private val projectService: ProjectService, + @Context private val uriInfo: UriInfo +) : ContainerRequestFilter { + override fun filter(requestContext: ContainerRequestContext) { + val resourceMethod = resourceInfo.resourceMethod + + val annotation = resourceMethod.getAnnotation(NeedsPermission::class.java) + + val permission = Permission(annotation.entity, annotation.operation) + + val projectId = annotation.projectPathParam + .takeIf { it.isNotEmpty() } + ?.let { uriInfo.pathParameters[it] } + ?.firstOrNull() + val userId = annotation.userPathParam + .takeIf { it.isNotEmpty() } + ?.let { uriInfo.pathParameters[it] } + ?.firstOrNull() + + val isAuthenticated = when { + userId != null -> projectId != null && auth.token.hasPermissionOnSubject(permission, projectId, userId) + projectId != null -> auth.token.hasPermissionOnProject(permission, projectId) + else -> auth.token.hasPermission(permission) + } + + if (!isAuthenticated) { + val message = "$permission permission not given." + throw HttpForbiddenException("insufficient_scope", message, additionalHeaders = listOf( + "WWW-Authenticate" to (AuthenticationFilter.BEARER_REALM + + " error=\"insufficient_scope\"" + + " error_description=\"$message\"" + + " scope=\"$permission\""))) + } + projectId?.let { projectService.ensureProject(it) } + } +} diff --git a/src/main/kotlin/org/radarbase/auth/jersey/impl/RadarSecurityContext.kt b/src/main/kotlin/org/radarbase/jersey/auth/filter/RadarSecurityContext.kt similarity index 94% rename from src/main/kotlin/org/radarbase/auth/jersey/impl/RadarSecurityContext.kt rename to src/main/kotlin/org/radarbase/jersey/auth/filter/RadarSecurityContext.kt index f16d9bc..82c2b2a 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/impl/RadarSecurityContext.kt +++ b/src/main/kotlin/org/radarbase/jersey/auth/filter/RadarSecurityContext.kt @@ -7,9 +7,9 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey.impl +package org.radarbase.jersey.auth.filter -import org.radarbase.auth.jersey.Auth +import org.radarbase.jersey.auth.Auth import java.security.Principal import javax.ws.rs.core.SecurityContext diff --git a/src/main/kotlin/org/radarbase/auth/jersey/impl/AuthFactory.kt b/src/main/kotlin/org/radarbase/jersey/auth/jwt/AuthFactory.kt similarity index 70% rename from src/main/kotlin/org/radarbase/auth/jersey/impl/AuthFactory.kt rename to src/main/kotlin/org/radarbase/jersey/auth/jwt/AuthFactory.kt index e463ba9..923ad3b 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/impl/AuthFactory.kt +++ b/src/main/kotlin/org/radarbase/jersey/auth/jwt/AuthFactory.kt @@ -7,18 +7,18 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey.impl +package org.radarbase.jersey.auth.jwt -import org.radarbase.auth.jersey.Auth +import org.radarbase.jersey.auth.Auth +import org.radarbase.jersey.auth.filter.RadarSecurityContext import java.util.function.Supplier import javax.ws.rs.container.ContainerRequestContext import javax.ws.rs.core.Context /** Generates radar tokens from the security context. */ -class AuthFactory : Supplier { - @Context - private lateinit var context: ContainerRequestContext - +class AuthFactory( + @Context private val context: ContainerRequestContext +) : Supplier { override fun get() = (context.securityContext as? RadarSecurityContext)?.auth ?: throw IllegalStateException("Created null wrapper") } diff --git a/src/main/kotlin/org/radarbase/auth/jersey/EcdsaJwtTokenValidator.kt b/src/main/kotlin/org/radarbase/jersey/auth/jwt/EcdsaJwtTokenValidator.kt similarity index 96% rename from src/main/kotlin/org/radarbase/auth/jersey/EcdsaJwtTokenValidator.kt rename to src/main/kotlin/org/radarbase/jersey/auth/jwt/EcdsaJwtTokenValidator.kt index ef8e4bd..30e73cc 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/EcdsaJwtTokenValidator.kt +++ b/src/main/kotlin/org/radarbase/jersey/auth/jwt/EcdsaJwtTokenValidator.kt @@ -7,7 +7,7 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey +package org.radarbase.jersey.auth.jwt import com.auth0.jwt.JWT import com.auth0.jwt.JWTVerifier @@ -15,8 +15,10 @@ import com.auth0.jwt.algorithms.Algorithm import com.auth0.jwt.exceptions.AlgorithmMismatchException import com.auth0.jwt.exceptions.JWTVerificationException import com.auth0.jwt.exceptions.SignatureVerificationException +import org.radarbase.jersey.auth.Auth +import org.radarbase.jersey.auth.AuthConfig +import org.radarbase.jersey.auth.AuthValidator import org.radarcns.auth.exception.ConfigurationException -import org.radarbase.auth.jersey.impl.JwtAuth import org.slf4j.Logger import org.slf4j.LoggerFactory import java.nio.file.Files diff --git a/src/main/kotlin/org/radarbase/auth/jersey/impl/JwtAuth.kt b/src/main/kotlin/org/radarbase/jersey/auth/jwt/JwtAuth.kt similarity index 96% rename from src/main/kotlin/org/radarbase/auth/jersey/impl/JwtAuth.kt rename to src/main/kotlin/org/radarbase/jersey/auth/jwt/JwtAuth.kt index d83c328..b17fbf4 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/impl/JwtAuth.kt +++ b/src/main/kotlin/org/radarbase/jersey/auth/jwt/JwtAuth.kt @@ -7,11 +7,11 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey.impl +package org.radarbase.jersey.auth.jwt import com.auth0.jwt.interfaces.DecodedJWT import com.fasterxml.jackson.databind.JsonNode -import org.radarbase.auth.jersey.Auth +import org.radarbase.jersey.auth.Auth import org.radarcns.auth.authorization.Permission import org.radarcns.auth.authorization.Permission.Entity import org.radarcns.auth.token.JwtRadarToken diff --git a/src/main/kotlin/org/radarbase/auth/jersey/impl/ManagementPortalAuth.kt b/src/main/kotlin/org/radarbase/jersey/auth/managementportal/ManagementPortalAuth.kt similarity index 82% rename from src/main/kotlin/org/radarbase/auth/jersey/impl/ManagementPortalAuth.kt rename to src/main/kotlin/org/radarbase/jersey/auth/managementportal/ManagementPortalAuth.kt index 1ef3576..cfbc49f 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/impl/ManagementPortalAuth.kt +++ b/src/main/kotlin/org/radarbase/jersey/auth/managementportal/ManagementPortalAuth.kt @@ -7,17 +7,14 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey.impl +package org.radarbase.jersey.auth.managementportal import com.auth0.jwt.interfaces.DecodedJWT import com.fasterxml.jackson.databind.JsonNode -import org.radarbase.auth.jersey.exception.HttpApplicationException -import org.radarbase.auth.jersey.Auth -import org.radarcns.auth.authorization.Permission +import org.radarbase.jersey.auth.Auth import org.radarcns.auth.authorization.Permission.MEASUREMENT_CREATE import org.radarcns.auth.token.JwtRadarToken import org.radarcns.auth.token.RadarToken -import javax.ws.rs.core.Response /** * Parsed JWT for validating authorization of data contents. diff --git a/src/main/kotlin/org/radarbase/auth/jersey/ManagementPortalTokenValidator.kt b/src/main/kotlin/org/radarbase/jersey/auth/managementportal/ManagementPortalTokenValidator.kt similarity index 89% rename from src/main/kotlin/org/radarbase/auth/jersey/ManagementPortalTokenValidator.kt rename to src/main/kotlin/org/radarbase/jersey/auth/managementportal/ManagementPortalTokenValidator.kt index 6a95c3f..e36ebb9 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/ManagementPortalTokenValidator.kt +++ b/src/main/kotlin/org/radarbase/jersey/auth/managementportal/ManagementPortalTokenValidator.kt @@ -7,15 +7,14 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey +package org.radarbase.jersey.auth.managementportal import com.auth0.jwt.JWT -import org.radarbase.auth.jersey.impl.ManagementPortalAuth +import org.radarbase.jersey.auth.Auth +import org.radarbase.jersey.auth.AuthValidator import org.radarcns.auth.authentication.TokenValidator -import org.radarcns.auth.config.TokenVerifierPublicKeyConfig import org.radarcns.auth.exception.TokenValidationException import org.slf4j.LoggerFactory -import java.net.URI import javax.ws.rs.container.ContainerRequestContext import javax.ws.rs.core.Context diff --git a/src/main/kotlin/org/radarbase/auth/jersey/impl/TokenValidatorFactory.kt b/src/main/kotlin/org/radarbase/jersey/auth/managementportal/TokenValidatorFactory.kt similarity index 79% rename from src/main/kotlin/org/radarbase/auth/jersey/impl/TokenValidatorFactory.kt rename to src/main/kotlin/org/radarbase/jersey/auth/managementportal/TokenValidatorFactory.kt index d530628..4f62896 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/impl/TokenValidatorFactory.kt +++ b/src/main/kotlin/org/radarbase/jersey/auth/managementportal/TokenValidatorFactory.kt @@ -7,16 +7,18 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey.impl +package org.radarbase.jersey.auth.managementportal -import org.radarbase.auth.jersey.AuthConfig +import org.radarbase.jersey.auth.AuthConfig import org.radarcns.auth.authentication.TokenValidator import org.radarcns.auth.config.TokenVerifierPublicKeyConfig import java.net.URI import java.util.function.Supplier import javax.ws.rs.core.Context -class TokenValidatorFactory(@Context private val config: AuthConfig) : Supplier { +class TokenValidatorFactory( + @Context private val config: AuthConfig +) : Supplier { override fun get(): TokenValidator = try { TokenValidator() } catch (e: RuntimeException) { diff --git a/src/main/kotlin/org/radarbase/auth/jersey/ApplicationResourceConfig.kt b/src/main/kotlin/org/radarbase/jersey/config/ApplicationResourceConfig.kt similarity index 89% rename from src/main/kotlin/org/radarbase/auth/jersey/ApplicationResourceConfig.kt rename to src/main/kotlin/org/radarbase/jersey/config/ApplicationResourceConfig.kt index cbe5844..7947399 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/ApplicationResourceConfig.kt +++ b/src/main/kotlin/org/radarbase/jersey/config/ApplicationResourceConfig.kt @@ -1,4 +1,4 @@ -package org.radarbase.auth.jersey +package org.radarbase.jersey.config import org.glassfish.jersey.internal.inject.AbstractBinder import org.glassfish.jersey.server.ResourceConfig diff --git a/src/main/kotlin/org/radarbase/auth/jersey/EcdsaResourceEnhancer.kt b/src/main/kotlin/org/radarbase/jersey/config/EcdsaResourceEnhancer.kt similarity index 81% rename from src/main/kotlin/org/radarbase/auth/jersey/EcdsaResourceEnhancer.kt rename to src/main/kotlin/org/radarbase/jersey/config/EcdsaResourceEnhancer.kt index 8ceba0d..2419762 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/EcdsaResourceEnhancer.kt +++ b/src/main/kotlin/org/radarbase/jersey/config/EcdsaResourceEnhancer.kt @@ -7,10 +7,11 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey +package org.radarbase.jersey.config import org.glassfish.jersey.internal.inject.AbstractBinder -import org.glassfish.jersey.server.ResourceConfig +import org.radarbase.jersey.auth.AuthValidator +import org.radarbase.jersey.auth.jwt.EcdsaJwtTokenValidator import javax.inject.Singleton /** @@ -21,7 +22,7 @@ import javax.inject.Singleton * and jwtKeystoreAlias. If jwtIssuer is set, the issuer of the JWT will also be validated. */ class EcdsaResourceEnhancer : JerseyResourceEnhancer { - override fun enhance(binder: AbstractBinder) { + override fun enhanceBinder(binder: AbstractBinder) { binder.bind(EcdsaJwtTokenValidator::class.java) .to(AuthValidator::class.java) .`in`(Singleton::class.java) diff --git a/src/main/kotlin/org/radarbase/jersey/config/GeneralExceptionResourceEnhancer.kt b/src/main/kotlin/org/radarbase/jersey/config/GeneralExceptionResourceEnhancer.kt new file mode 100644 index 0000000..bb8f873 --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/config/GeneralExceptionResourceEnhancer.kt @@ -0,0 +1,13 @@ +package org.radarbase.jersey.config + +import org.glassfish.jersey.server.ResourceConfig +import org.radarbase.appconfig.exception.UnhandledExceptionMapper +import org.radarbase.appconfig.exception.WebApplicationExceptionMapper + +class GeneralExceptionResourceEnhancer: JerseyResourceEnhancer { + override fun enhanceResources(resourceConfig: ResourceConfig) { + resourceConfig.registerClasses( + UnhandledExceptionMapper::class.java, + WebApplicationExceptionMapper::class.java) + } +} diff --git a/src/main/kotlin/org/radarbase/jersey/config/HttpExceptionResourceEnhancer.kt b/src/main/kotlin/org/radarbase/jersey/config/HttpExceptionResourceEnhancer.kt new file mode 100644 index 0000000..f230ef7 --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/config/HttpExceptionResourceEnhancer.kt @@ -0,0 +1,20 @@ +package org.radarbase.jersey.config + +import org.glassfish.jersey.internal.inject.AbstractBinder +import org.glassfish.jersey.internal.inject.PerThread +import org.glassfish.jersey.server.ResourceConfig +import org.radarbase.jersey.exception.mapper.DefaultExceptionHtmlRenderer +import org.radarbase.jersey.exception.mapper.ExceptionHtmlRenderer +import org.radarbase.jersey.inject.HttpApplicationExceptionMapper + +class HttpExceptionResourceEnhancer: JerseyResourceEnhancer { + override fun enhanceBinder(binder: AbstractBinder) { + binder.bind(DefaultExceptionHtmlRenderer::class.java) + .to(ExceptionHtmlRenderer::class.java) + .`in`(PerThread::class.java) + } + + override fun enhanceResources(resourceConfig: ResourceConfig) { + resourceConfig.registerClasses(HttpApplicationExceptionMapper::class.java) + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/radarbase/auth/jersey/JerseyResourceEnhancer.kt b/src/main/kotlin/org/radarbase/jersey/config/JerseyResourceEnhancer.kt similarity index 62% rename from src/main/kotlin/org/radarbase/auth/jersey/JerseyResourceEnhancer.kt rename to src/main/kotlin/org/radarbase/jersey/config/JerseyResourceEnhancer.kt index b66d9d4..d583432 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/JerseyResourceEnhancer.kt +++ b/src/main/kotlin/org/radarbase/jersey/config/JerseyResourceEnhancer.kt @@ -7,12 +7,12 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey +package org.radarbase.jersey.config import org.glassfish.jersey.internal.inject.AbstractBinder +import org.glassfish.jersey.server.ResourceConfig interface JerseyResourceEnhancer { - val packages: Array - get() = arrayOf() - fun enhance(binder: AbstractBinder) + fun enhanceResources(resourceConfig: ResourceConfig) = Unit + fun enhanceBinder(binder: AbstractBinder) = Unit } diff --git a/src/main/kotlin/org/radarbase/auth/jersey/ManagementPortalResourceEnhancer.kt b/src/main/kotlin/org/radarbase/jersey/config/ManagementPortalResourceEnhancer.kt similarity index 76% rename from src/main/kotlin/org/radarbase/auth/jersey/ManagementPortalResourceEnhancer.kt rename to src/main/kotlin/org/radarbase/jersey/config/ManagementPortalResourceEnhancer.kt index 8650a94..a31cc0f 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/ManagementPortalResourceEnhancer.kt +++ b/src/main/kotlin/org/radarbase/jersey/config/ManagementPortalResourceEnhancer.kt @@ -7,11 +7,12 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey +package org.radarbase.jersey.config import org.glassfish.jersey.internal.inject.AbstractBinder -import org.glassfish.jersey.server.ResourceConfig -import org.radarbase.auth.jersey.impl.TokenValidatorFactory +import org.radarbase.jersey.auth.AuthValidator +import org.radarbase.jersey.auth.managementportal.ManagementPortalTokenValidator +import org.radarbase.jersey.auth.managementportal.TokenValidatorFactory import org.radarcns.auth.authentication.TokenValidator import javax.inject.Singleton @@ -21,7 +22,7 @@ import javax.inject.Singleton * It requires managementPortalUrl and jwtResourceName to be set in the AuthConfig. */ class ManagementPortalResourceEnhancer : JerseyResourceEnhancer { - override fun enhance(binder: AbstractBinder) { + override fun enhanceBinder(binder: AbstractBinder) { binder.apply { bindFactory(TokenValidatorFactory::class.java) .to(TokenValidator::class.java) diff --git a/src/main/kotlin/org/radarbase/auth/jersey/RadarJerseyResourceEnhancer.kt b/src/main/kotlin/org/radarbase/jersey/config/RadarJerseyResourceEnhancer.kt similarity index 54% rename from src/main/kotlin/org/radarbase/auth/jersey/RadarJerseyResourceEnhancer.kt rename to src/main/kotlin/org/radarbase/jersey/config/RadarJerseyResourceEnhancer.kt index 0356902..a7e0061 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/RadarJerseyResourceEnhancer.kt +++ b/src/main/kotlin/org/radarbase/jersey/config/RadarJerseyResourceEnhancer.kt @@ -7,23 +7,34 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey +package org.radarbase.jersey.config import org.glassfish.jersey.internal.inject.AbstractBinder -import org.glassfish.jersey.internal.inject.PerThread import org.glassfish.jersey.process.internal.RequestScoped -import org.radarbase.auth.jersey.exception.DefaultExceptionHtmlRenderer -import org.radarbase.auth.jersey.exception.ExceptionHtmlRenderer -import org.radarbase.auth.jersey.impl.AuthFactory +import org.glassfish.jersey.server.ResourceConfig +import org.radarbase.jersey.auth.Auth +import org.radarbase.jersey.auth.AuthConfig +import org.radarbase.jersey.auth.jwt.AuthFactory +import org.radarbase.jersey.auth.filter.AuthenticationFilter +import org.radarbase.jersey.auth.filter.AuthorizationFeature +import kotlin.apply +import kotlin.jvm.java /** * Add RADAR auth to a Jersey project. This requires a {@link ProjectService} implementation to be * added to the Binder first. */ -class RadarJerseyResourceEnhancer(private val config: AuthConfig): JerseyResourceEnhancer { - override val packages = arrayOf("org.radarbase.auth.jersey.inject") +class RadarJerseyResourceEnhancer( + private val config: AuthConfig +): JerseyResourceEnhancer { + override fun enhanceResources(resourceConfig: ResourceConfig) { + resourceConfig.registerClasses( + AuthenticationFilter::class.java, + AuthorizationFeature::class.java + ) + } - override fun enhance(binder: AbstractBinder) { + override fun enhanceBinder(binder: AbstractBinder) { binder.apply { bind(config) .to(AuthConfig::class.java) @@ -34,11 +45,6 @@ class RadarJerseyResourceEnhancer(private val config: AuthConfig): JerseyResourc .proxyForSameScope(false) .to(Auth::class.java) .`in`(RequestScoped::class.java) - - bind(DefaultExceptionHtmlRenderer::class.java) - .to(ExceptionHtmlRenderer::class.java) - .`in`(PerThread::class.java) - .ranked(10) } } } diff --git a/src/main/kotlin/org/radarbase/auth/jersey/RadarResourceConfigFactory.kt b/src/main/kotlin/org/radarbase/jersey/config/RadarResourceConfigFactory.kt similarity index 52% rename from src/main/kotlin/org/radarbase/auth/jersey/RadarResourceConfigFactory.kt rename to src/main/kotlin/org/radarbase/jersey/config/RadarResourceConfigFactory.kt index 06be864..b456ea7 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/RadarResourceConfigFactory.kt +++ b/src/main/kotlin/org/radarbase/jersey/config/RadarResourceConfigFactory.kt @@ -1,20 +1,17 @@ -package org.radarbase.auth.jersey +package org.radarbase.jersey.config import org.glassfish.jersey.internal.inject.AbstractBinder import org.glassfish.jersey.server.ResourceConfig class RadarResourceConfigFactory { - fun resources(appConfig: ApplicationResourceConfig): ResourceConfig { - val enhancers = appConfig.createEnhancers() + fun resources(enhancers: List): ResourceConfig { val resources = ResourceConfig() resources.property("jersey.config.server.wadl.disableWadl", true) - appConfig.configureResources(resources) - enhancers.forEach { resources.packages(*it.packages) } + enhancers.forEach { it.enhanceResources(resources) } resources.register(object : AbstractBinder() { override fun configure() { - appConfig.configureBinder(this) - enhancers.forEach { it.enhance(this) } + enhancers.forEach { it.enhanceBinder(this) } } }) return resources diff --git a/src/main/kotlin/org/radarbase/auth/jersey/exception/HttpApplicationException.kt b/src/main/kotlin/org/radarbase/jersey/exception/HttpApplicationException.kt similarity index 83% rename from src/main/kotlin/org/radarbase/auth/jersey/exception/HttpApplicationException.kt rename to src/main/kotlin/org/radarbase/jersey/exception/HttpApplicationException.kt index f0a0ce9..eeaf316 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/exception/HttpApplicationException.kt +++ b/src/main/kotlin/org/radarbase/jersey/exception/HttpApplicationException.kt @@ -7,11 +7,12 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey.exception +package org.radarbase.jersey.exception import java.lang.RuntimeException import javax.ws.rs.core.Response open class HttpApplicationException(val status: Int, val code: String, val detailedMessage: String? = null, val additionalHeaders: List> = listOf()) : RuntimeException("[$status] $code: ${detailedMessage ?: "no message"}") { - constructor(status: Response.Status, code: String, detailedMessage: String? = null, additionalHeaders: List> = listOf()) : this(status.statusCode, code, detailedMessage, additionalHeaders) + constructor(status: Response.Status, code: String, detailedMessage: String? = null, additionalHeaders: List> = listOf()) + : this(status.statusCode, code, detailedMessage, additionalHeaders) } diff --git a/src/main/kotlin/org/radarbase/jersey/exception/HttpBadGatewayException.kt b/src/main/kotlin/org/radarbase/jersey/exception/HttpBadGatewayException.kt new file mode 100644 index 0000000..d1910fd --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/exception/HttpBadGatewayException.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2019. The Hyve + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * See the file LICENSE in the root of this repository. + */ + +package org.radarbase.jersey.exception + +import javax.ws.rs.core.Response.Status + +class HttpBadGatewayException(message: String) + : HttpApplicationException(Status.BAD_GATEWAY, "bad_gateway", message) diff --git a/src/main/kotlin/org/radarbase/jersey/exception/HttpBadRequestException.kt b/src/main/kotlin/org/radarbase/jersey/exception/HttpBadRequestException.kt new file mode 100644 index 0000000..b2fe945 --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/exception/HttpBadRequestException.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2019. The Hyve + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * See the file LICENSE in the root of this repository. + */ + +package org.radarbase.jersey.exception + +import javax.ws.rs.core.Response + +class HttpBadRequestException(code: String, message: String) : + HttpApplicationException(Response.Status.BAD_REQUEST, code, message) diff --git a/src/main/kotlin/org/radarbase/jersey/exception/HttpConflictException.kt b/src/main/kotlin/org/radarbase/jersey/exception/HttpConflictException.kt new file mode 100644 index 0000000..0b650f6 --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/exception/HttpConflictException.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2019. The Hyve + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * See the file LICENSE in the root of this repository. + */ + +package org.radarbase.jersey.exception + +import javax.ws.rs.core.Response + +class HttpConflictException(code: String, messageText: String) : + HttpApplicationException(Response.Status.CONFLICT, code, messageText) diff --git a/src/main/kotlin/org/radarbase/jersey/exception/HttpForbiddenException.kt b/src/main/kotlin/org/radarbase/jersey/exception/HttpForbiddenException.kt new file mode 100644 index 0000000..496e4b0 --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/exception/HttpForbiddenException.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2019. The Hyve + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * See the file LICENSE in the root of this repository. + */ + +package org.radarbase.jersey.exception + +import javax.ws.rs.core.Response + +class HttpForbiddenException(code: String, detailedMessage: String, additionalHeaders: List> = listOf()) + : HttpApplicationException(Response.Status.FORBIDDEN, code, detailedMessage, additionalHeaders) diff --git a/src/main/kotlin/org/radarbase/jersey/exception/HttpGatewayTimeoutException.kt b/src/main/kotlin/org/radarbase/jersey/exception/HttpGatewayTimeoutException.kt new file mode 100644 index 0000000..957ac75 --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/exception/HttpGatewayTimeoutException.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2019. The Hyve + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * See the file LICENSE in the root of this repository. + */ + +package org.radarbase.jersey.exception + +import javax.ws.rs.core.Response.Status + +class HttpGatewayTimeoutException(message: String) + : HttpApplicationException(Status.GATEWAY_TIMEOUT, "gateway_timeout", message) diff --git a/src/main/kotlin/org/radarbase/jersey/exception/HttpInternalServerException.kt b/src/main/kotlin/org/radarbase/jersey/exception/HttpInternalServerException.kt new file mode 100644 index 0000000..f756d0d --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/exception/HttpInternalServerException.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2019. The Hyve + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * See the file LICENSE in the root of this repository. + */ + +package org.radarbase.jersey.exception + +import javax.ws.rs.core.Response + +class HttpInternalServerException(code: String, message: String) : + HttpApplicationException(Response.Status.INTERNAL_SERVER_ERROR, code, message) diff --git a/src/main/kotlin/org/radarbase/jersey/exception/HttpInvalidContentException.kt b/src/main/kotlin/org/radarbase/jersey/exception/HttpInvalidContentException.kt new file mode 100644 index 0000000..5b21358 --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/exception/HttpInvalidContentException.kt @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2019. The Hyve + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * See the file LICENSE in the root of this repository. + */ + +package org.radarbase.jersey.exception + +class HttpInvalidContentException(s: String) + : HttpApplicationException(422, "invalid_content", s) diff --git a/src/main/kotlin/org/radarbase/jersey/exception/HttpNotFoundException.kt b/src/main/kotlin/org/radarbase/jersey/exception/HttpNotFoundException.kt new file mode 100644 index 0000000..0ad4507 --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/exception/HttpNotFoundException.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2019. The Hyve + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * See the file LICENSE in the root of this repository. + */ + +package org.radarbase.jersey.exception + +import javax.ws.rs.core.Response + +class HttpNotFoundException(code: String, message: String) : + HttpApplicationException(Response.Status.NOT_FOUND, code, message) diff --git a/src/main/kotlin/org/radarbase/jersey/exception/HttpRequestEntityTooLarge.kt b/src/main/kotlin/org/radarbase/jersey/exception/HttpRequestEntityTooLarge.kt new file mode 100644 index 0000000..30a810f --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/exception/HttpRequestEntityTooLarge.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2019. The Hyve + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * See the file LICENSE in the root of this repository. + */ + +package org.radarbase.jersey.exception + +import javax.ws.rs.core.Response + +class HttpRequestEntityTooLarge(message: String) + : HttpApplicationException(Response.Status.REQUEST_ENTITY_TOO_LARGE, "request_entity_too_large", message) diff --git a/src/main/kotlin/org/radarbase/jersey/exception/HttpUnauthorizedException.kt b/src/main/kotlin/org/radarbase/jersey/exception/HttpUnauthorizedException.kt new file mode 100644 index 0000000..a0c087c --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/exception/HttpUnauthorizedException.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2019. The Hyve + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * See the file LICENSE in the root of this repository. + */ + +package org.radarbase.jersey.exception + +import javax.ws.rs.core.Response + +class HttpUnauthorizedException(code: String, detailedMessage: String, additionalHeaders: List>) + : HttpApplicationException(Response.Status.UNAUTHORIZED, code, detailedMessage, additionalHeaders) diff --git a/src/main/kotlin/org/radarbase/auth/jersey/exception/DefaultExceptionHtmlRenderer.kt b/src/main/kotlin/org/radarbase/jersey/exception/mapper/DefaultExceptionHtmlRenderer.kt similarity index 95% rename from src/main/kotlin/org/radarbase/auth/jersey/exception/DefaultExceptionHtmlRenderer.kt rename to src/main/kotlin/org/radarbase/jersey/exception/mapper/DefaultExceptionHtmlRenderer.kt index 1c5bd03..d482778 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/exception/DefaultExceptionHtmlRenderer.kt +++ b/src/main/kotlin/org/radarbase/jersey/exception/mapper/DefaultExceptionHtmlRenderer.kt @@ -7,16 +7,16 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey.exception +package org.radarbase.jersey.exception.mapper import com.github.mustachejava.DefaultMustacheFactory import com.github.mustachejava.Mustache +import org.radarbase.jersey.exception.HttpApplicationException import org.slf4j.LoggerFactory import java.io.ByteArrayOutputStream import java.io.IOException import java.io.OutputStreamWriter - class DefaultExceptionHtmlRenderer: ExceptionHtmlRenderer { private val errorTemplates: Map diff --git a/src/main/kotlin/org/radarbase/auth/jersey/exception/ExceptionHtmlRenderer.kt b/src/main/kotlin/org/radarbase/jersey/exception/mapper/ExceptionHtmlRenderer.kt similarity index 75% rename from src/main/kotlin/org/radarbase/auth/jersey/exception/ExceptionHtmlRenderer.kt rename to src/main/kotlin/org/radarbase/jersey/exception/mapper/ExceptionHtmlRenderer.kt index f9bf9b9..2bee946 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/exception/ExceptionHtmlRenderer.kt +++ b/src/main/kotlin/org/radarbase/jersey/exception/mapper/ExceptionHtmlRenderer.kt @@ -7,7 +7,9 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey.exception +package org.radarbase.jersey.exception.mapper + +import org.radarbase.jersey.exception.HttpApplicationException interface ExceptionHtmlRenderer { fun render(exception: HttpApplicationException): String diff --git a/src/main/kotlin/org/radarbase/auth/jersey/inject/HttpApplicationExceptionMapper.kt b/src/main/kotlin/org/radarbase/jersey/exception/mapper/HttpApplicationExceptionMapper.kt similarity index 84% rename from src/main/kotlin/org/radarbase/auth/jersey/inject/HttpApplicationExceptionMapper.kt rename to src/main/kotlin/org/radarbase/jersey/exception/mapper/HttpApplicationExceptionMapper.kt index 53d19e1..7d63b22 100644 --- a/src/main/kotlin/org/radarbase/auth/jersey/inject/HttpApplicationExceptionMapper.kt +++ b/src/main/kotlin/org/radarbase/jersey/exception/mapper/HttpApplicationExceptionMapper.kt @@ -7,11 +7,11 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey.inject +package org.radarbase.jersey.inject import com.fasterxml.jackson.core.util.BufferRecyclers -import org.radarbase.auth.jersey.exception.ExceptionHtmlRenderer -import org.radarbase.auth.jersey.exception.HttpApplicationException +import org.radarbase.jersey.exception.mapper.ExceptionHtmlRenderer +import org.radarbase.jersey.exception.HttpApplicationException import org.slf4j.LoggerFactory import javax.inject.Singleton import javax.ws.rs.container.ContainerRequestContext @@ -25,14 +25,11 @@ import kotlin.text.Charsets.UTF_8 @Provider @Singleton -class HttpApplicationExceptionMapper : ExceptionMapper { - @Context - private lateinit var uriInfo: UriInfo - - @Context lateinit var requestContext: ContainerRequestContext - - @Context lateinit var htmlRenderer: ExceptionHtmlRenderer - +class HttpApplicationExceptionMapper( + @Context private val uriInfo: UriInfo, + @Context private val requestContext: ContainerRequestContext, + @Context private val htmlRenderer: ExceptionHtmlRenderer +) : ExceptionMapper { override fun toResponse(exception: HttpApplicationException): Response { var mediaType = requestContext.acceptableMediaTypes .firstOrNull { type -> type in setOf(MediaType.WILDCARD_TYPE, MediaType.APPLICATION_JSON_TYPE, MediaType.TEXT_HTML_TYPE, MediaType.TEXT_PLAIN) } diff --git a/src/main/kotlin/org/radarbase/jersey/exception/mapper/UnhandledExceptionMapper.kt b/src/main/kotlin/org/radarbase/jersey/exception/mapper/UnhandledExceptionMapper.kt new file mode 100644 index 0000000..7684013 --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/exception/mapper/UnhandledExceptionMapper.kt @@ -0,0 +1,31 @@ +package org.radarbase.appconfig.exception + +import org.slf4j.LoggerFactory +import javax.inject.Singleton +import javax.ws.rs.core.Context +import javax.ws.rs.core.Response +import javax.ws.rs.core.UriInfo +import javax.ws.rs.ext.ExceptionMapper +import javax.ws.rs.ext.Provider + +/** Handle exceptions without a specific mapper. */ +@Provider +@Singleton +class UnhandledExceptionMapper( + @Context private val uriInfo: UriInfo +) : ExceptionMapper { + + + override fun toResponse(exception: Throwable): Response { + logger.error("[500] {}", uriInfo.absolutePath, exception) + return Response.serverError() + .header("Content-Type", "application/json; charset=utf-8") + .entity("{\"error\":\"unknown\"," + + "\"error_description\":\"Unknown exception.\"}") + .build() + } + + companion object { + private val logger = LoggerFactory.getLogger(UnhandledExceptionMapper::class.java) + } +} diff --git a/src/main/kotlin/org/radarbase/jersey/exception/mapper/WebApplicationExceptionMapper.kt b/src/main/kotlin/org/radarbase/jersey/exception/mapper/WebApplicationExceptionMapper.kt new file mode 100644 index 0000000..b0a5ee5 --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/exception/mapper/WebApplicationExceptionMapper.kt @@ -0,0 +1,28 @@ +package org.radarbase.appconfig.exception + +import org.slf4j.LoggerFactory +import javax.inject.Singleton +import javax.ws.rs.WebApplicationException +import javax.ws.rs.core.Context +import javax.ws.rs.core.Response +import javax.ws.rs.core.UriInfo +import javax.ws.rs.ext.ExceptionMapper +import javax.ws.rs.ext.Provider + +/** Handle WebApplicationException. This uses the status code embedded in the exception. */ +@Provider +@Singleton +class WebApplicationExceptionMapper( + @Context private val uriInfo: UriInfo +) : ExceptionMapper { + + override fun toResponse(exception: WebApplicationException): Response { + val response = exception.response + logger.error("[{}] {}: {}", response.status, uriInfo.absolutePath, exception.message) + return response + } + + companion object { + private val logger = LoggerFactory.getLogger(WebApplicationExceptionMapper::class.java) + } +} diff --git a/src/main/resources/org/radarbase/auth/jersey/exception/4xx.html b/src/main/resources/org/radarbase/jersey/exception/mapper/4xx.html similarity index 100% rename from src/main/resources/org/radarbase/auth/jersey/exception/4xx.html rename to src/main/resources/org/radarbase/jersey/exception/mapper/4xx.html diff --git a/src/main/resources/org/radarbase/auth/jersey/exception/5xx.html b/src/main/resources/org/radarbase/jersey/exception/mapper/5xx.html similarity index 100% rename from src/main/resources/org/radarbase/auth/jersey/exception/5xx.html rename to src/main/resources/org/radarbase/jersey/exception/mapper/5xx.html diff --git a/src/test/kotlin/org/radarbase/auth/jersey/exception/DefaultExceptionHtmlRendererTest.kt b/src/test/kotlin/org/radarbase/auth/jersey/exception/DefaultExceptionHtmlRendererTest.kt deleted file mode 100644 index cd2aa14..0000000 --- a/src/test/kotlin/org/radarbase/auth/jersey/exception/DefaultExceptionHtmlRendererTest.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2019. The Hyve - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * - * See the file LICENSE in the root of this repository. - */ - -package org.radarbase.auth.jersey.exception - -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.containsString -import org.junit.jupiter.api.Test -import org.radarbase.auth.jersey.exception.DefaultExceptionHtmlRenderer -import org.radarbase.auth.jersey.exception.HttpApplicationException -import javax.ws.rs.core.Response - -internal class DefaultExceptionHtmlRendererTest { - - @Test - fun render4xx() { - val renderer = DefaultExceptionHtmlRenderer() - val ex = HttpApplicationException(Response.Status.BAD_REQUEST, "code", "message") - val result = renderer.render(ex) - - assertThat(result, containsString("

Bad request (status code 400)

")) - assertThat(result, containsString("

code - message

")) - } - - @Test - fun render5xx() { - val renderer = DefaultExceptionHtmlRenderer() - val ex = HttpApplicationException(Response.Status.INTERNAL_SERVER_ERROR, "code", "message") - val result = renderer.render(ex) - - assertThat(result, containsString("

Server error (status code 500)

")) - assertThat(result, containsString("

code - message

")) - } -} diff --git a/src/test/kotlin/org/radarbase/auth/jersey/OAuthHelper.kt b/src/test/kotlin/org/radarbase/jersey/auth/OAuthHelper.kt similarity index 89% rename from src/test/kotlin/org/radarbase/auth/jersey/OAuthHelper.kt rename to src/test/kotlin/org/radarbase/jersey/auth/OAuthHelper.kt index 963a415..e50328b 100644 --- a/src/test/kotlin/org/radarbase/auth/jersey/OAuthHelper.kt +++ b/src/test/kotlin/org/radarbase/jersey/auth/OAuthHelper.kt @@ -1,4 +1,4 @@ -package org.radarbase.auth.jersey +package org.radarbase.jersey.auth import com.auth0.jwt.JWT import com.auth0.jwt.algorithms.Algorithm @@ -25,13 +25,13 @@ class OAuthHelper { val ks = KeyStore.getInstance("PKCS12") val resource = checkNotNull(javaClass.getResourceAsStream("/config/keystore.p12")) { "Failed to load key store" } resource.use { keyStream -> - ks.load(keyStream, Companion.TEST_KEYSTORE_PASSWORD.toCharArray()) + ks.load(keyStream, TEST_KEYSTORE_PASSWORD.toCharArray()) } // get the EC keypair for signing - val privateKey = ks.getKey(Companion.TEST_SIGNKEY_ALIAS, - Companion.TEST_KEYSTORE_PASSWORD.toCharArray()) as ECPrivateKey - val cert = ks.getCertificate(Companion.TEST_SIGNKEY_ALIAS) + val privateKey = ks.getKey(TEST_SIGNKEY_ALIAS, + TEST_KEYSTORE_PASSWORD.toCharArray()) as ECPrivateKey + val cert = ks.getCertificate(TEST_SIGNKEY_ALIAS) val publicKey = cert.publicKey as ECPublicKey val ecdsa = Algorithm.ECDSA256(publicKey, privateKey) @@ -61,7 +61,7 @@ class OAuthHelper { .withExpiresAt(Date.from(exp)) .withAudience("res_ManagementPortal") .withSubject(USER) - .withArrayClaim("scope", Companion.SCOPES) + .withArrayClaim("scope", SCOPES) .withArrayClaim("authorities", AUTHORITIES) .withArrayClaim("roles", ROLES) .withArrayClaim("sources", SOURCES) diff --git a/src/test/kotlin/org/radarbase/auth/jersey/RadarJerseyResourceEnhancerTest.kt b/src/test/kotlin/org/radarbase/jersey/auth/RadarJerseyResourceEnhancerTest.kt similarity index 96% rename from src/test/kotlin/org/radarbase/auth/jersey/RadarJerseyResourceEnhancerTest.kt rename to src/test/kotlin/org/radarbase/jersey/auth/RadarJerseyResourceEnhancerTest.kt index bb7d3f6..f068ba9 100644 --- a/src/test/kotlin/org/radarbase/auth/jersey/RadarJerseyResourceEnhancerTest.kt +++ b/src/test/kotlin/org/radarbase/jersey/auth/RadarJerseyResourceEnhancerTest.kt @@ -7,7 +7,7 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey +package org.radarbase.jersey.auth import okhttp3.OkHttpClient import okhttp3.Request @@ -21,8 +21,9 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.radarbase.auth.jersey.mock.MockProjectService -import org.radarbase.auth.jersey.OAuthHelper.Companion.bearerHeader +import org.radarbase.jersey.config.ManagementPortalResourceEnhancer +import org.radarbase.jersey.config.RadarJerseyResourceEnhancer +import org.radarbase.jersey.mock.MockProjectService import org.radarcns.auth.authentication.TokenValidator import java.net.URI import javax.inject.Singleton diff --git a/src/test/kotlin/org/radarbase/auth/jersey/mock/MockProjectService.kt b/src/test/kotlin/org/radarbase/jersey/mock/MockProjectService.kt similarity index 94% rename from src/test/kotlin/org/radarbase/auth/jersey/mock/MockProjectService.kt rename to src/test/kotlin/org/radarbase/jersey/mock/MockProjectService.kt index 54e4c3e..139098f 100644 --- a/src/test/kotlin/org/radarbase/auth/jersey/mock/MockProjectService.kt +++ b/src/test/kotlin/org/radarbase/jersey/mock/MockProjectService.kt @@ -7,7 +7,7 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey.mock +package org.radarbase.jersey.mock import org.radarbase.auth.jersey.ProjectService import org.radarbase.auth.jersey.exception.HttpApplicationException diff --git a/src/test/kotlin/org/radarbase/auth/jersey/mock/resource/MockResource.kt b/src/test/kotlin/org/radarbase/jersey/mock/resource/MockResource.kt similarity index 96% rename from src/test/kotlin/org/radarbase/auth/jersey/mock/resource/MockResource.kt rename to src/test/kotlin/org/radarbase/jersey/mock/resource/MockResource.kt index eb9a061..af980cb 100644 --- a/src/test/kotlin/org/radarbase/auth/jersey/mock/resource/MockResource.kt +++ b/src/test/kotlin/org/radarbase/jersey/mock/resource/MockResource.kt @@ -7,7 +7,7 @@ * See the file LICENSE in the root of this repository. */ -package org.radarbase.auth.jersey.mock.resource +package org.radarbase.jersey.mock.resource import org.radarbase.auth.jersey.Auth import org.radarbase.auth.jersey.Authenticated From 7451dd186d920c1bdebc29c59381fcb30286fd37 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 7 Oct 2019 10:12:13 +0200 Subject: [PATCH 05/13] Fix tests --- .../org/radarbase/jersey/auth/OAuthHelper.kt | 1 + .../auth/RadarJerseyResourceEnhancerTest.kt | 43 +++++-------------- .../jersey/mock/MockProjectService.kt | 7 ++- .../jersey/mock/MockResourceEnhancer.kt | 24 +++++++++++ .../jersey/mock/resource/MockResource.kt | 6 +-- 5 files changed, 42 insertions(+), 39 deletions(-) create mode 100644 src/test/kotlin/org/radarbase/jersey/mock/MockResourceEnhancer.kt diff --git a/src/test/kotlin/org/radarbase/jersey/auth/OAuthHelper.kt b/src/test/kotlin/org/radarbase/jersey/auth/OAuthHelper.kt index e50328b..365f4f2 100644 --- a/src/test/kotlin/org/radarbase/jersey/auth/OAuthHelper.kt +++ b/src/test/kotlin/org/radarbase/jersey/auth/OAuthHelper.kt @@ -45,6 +45,7 @@ class OAuthHelper { override fun getPublicKeys(): List = emptyList() } + @Suppress("DEPRECATION") tokenValidator = object : TokenValidator(verifiers, validatorConfig) { override fun refresh() { // do nothing diff --git a/src/test/kotlin/org/radarbase/jersey/auth/RadarJerseyResourceEnhancerTest.kt b/src/test/kotlin/org/radarbase/jersey/auth/RadarJerseyResourceEnhancerTest.kt index f068ba9..7195c61 100644 --- a/src/test/kotlin/org/radarbase/jersey/auth/RadarJerseyResourceEnhancerTest.kt +++ b/src/test/kotlin/org/radarbase/jersey/auth/RadarJerseyResourceEnhancerTest.kt @@ -13,20 +13,16 @@ import okhttp3.OkHttpClient import okhttp3.Request import org.glassfish.grizzly.http.server.HttpServer import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory -import org.glassfish.jersey.internal.inject.AbstractBinder -import org.glassfish.jersey.server.ResourceConfig import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.* import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.radarbase.jersey.config.ManagementPortalResourceEnhancer -import org.radarbase.jersey.config.RadarJerseyResourceEnhancer -import org.radarbase.jersey.mock.MockProjectService -import org.radarcns.auth.authentication.TokenValidator +import org.radarbase.jersey.auth.OAuthHelper.Companion.bearerHeader +import org.radarbase.jersey.config.* +import org.radarbase.jersey.mock.MockResourceEnhancer import java.net.URI -import javax.inject.Singleton internal class RadarJerseyResourceEnhancerTest { private lateinit var client: OkHttpClient @@ -38,31 +34,14 @@ internal class RadarJerseyResourceEnhancerTest { managementPortalUrl = "http://localhost:8080", jwtResourceName = "res_ManagementPortal") - val radarEnhancer = RadarJerseyResourceEnhancer(authConfig) - val mpEnhancer = ManagementPortalResourceEnhancer() - - val resourceConfig = object : ResourceConfig() { - init { - packages("org.radarbase.auth.jersey.mock.resource") - packages(*radarEnhancer.packages) - packages(*mpEnhancer.packages) - } - } - - resourceConfig.register(object : AbstractBinder() { - override fun configure() { - bind(MockProjectService(listOf("a", "b"))) - .to(ProjectService::class.java) - .`in`(Singleton::class.java) - - bindFactory { oauthHelper.tokenValidator } - .to(TokenValidator::class.java) - - radarEnhancer.enhance(this) - mpEnhancer.enhance(this) - } - }) + val enhancers = listOf( + MockResourceEnhancer(), + RadarJerseyResourceEnhancer(authConfig), + ManagementPortalResourceEnhancer(), + HttpExceptionResourceEnhancer(), + GeneralExceptionResourceEnhancer()) + val resourceConfig = RadarResourceConfigFactory().resources(enhancers) server = GrizzlyHttpServerFactory.createHttpServer(URI.create("http://localhost:9091"), resourceConfig) server.start() @@ -152,7 +131,7 @@ internal class RadarJerseyResourceEnhancerTest { assertThat(response.isSuccessful, `is`(false)) assertThat(response.code, `is`(404)) - assertThat(response.body?.string(), equalTo("{\"error\":\"project_not_found\",\"error_description\":null}")) + assertThat(response.body?.string(), equalTo("{\"error\":\"project_not_found\",\"error_description\":\"Project c not found.\"}")) } } diff --git a/src/test/kotlin/org/radarbase/jersey/mock/MockProjectService.kt b/src/test/kotlin/org/radarbase/jersey/mock/MockProjectService.kt index 139098f..588f3c0 100644 --- a/src/test/kotlin/org/radarbase/jersey/mock/MockProjectService.kt +++ b/src/test/kotlin/org/radarbase/jersey/mock/MockProjectService.kt @@ -9,14 +9,13 @@ package org.radarbase.jersey.mock -import org.radarbase.auth.jersey.ProjectService -import org.radarbase.auth.jersey.exception.HttpApplicationException -import javax.ws.rs.core.Response +import org.radarbase.jersey.auth.ProjectService +import org.radarbase.jersey.exception.HttpNotFoundException class MockProjectService(private val projects: List) : ProjectService { override fun ensureProject(projectId: String) { if (projectId !in projects) { - throw HttpApplicationException(Response.Status.NOT_FOUND, "project_not_found") + throw HttpNotFoundException("project_not_found", "Project $projectId not found.") } } } diff --git a/src/test/kotlin/org/radarbase/jersey/mock/MockResourceEnhancer.kt b/src/test/kotlin/org/radarbase/jersey/mock/MockResourceEnhancer.kt new file mode 100644 index 0000000..80d0c37 --- /dev/null +++ b/src/test/kotlin/org/radarbase/jersey/mock/MockResourceEnhancer.kt @@ -0,0 +1,24 @@ +package org.radarbase.jersey.mock + +import org.glassfish.jersey.internal.inject.AbstractBinder +import org.glassfish.jersey.server.ResourceConfig +import org.radarbase.jersey.auth.ProjectService +import org.radarbase.jersey.auth.RadarJerseyResourceEnhancerTest +import org.radarbase.jersey.config.JerseyResourceEnhancer +import org.radarcns.auth.authentication.TokenValidator +import javax.inject.Singleton + +class MockResourceEnhancer : JerseyResourceEnhancer { + override fun enhanceResources(resourceConfig: ResourceConfig) { + resourceConfig.packages("org.radarbase.jersey.mock.resource") + } + + override fun enhanceBinder(binder: AbstractBinder) { + binder.bind(MockProjectService(listOf("a", "b"))) + .to(ProjectService::class.java) + .`in`(Singleton::class.java) + + binder.bindFactory { RadarJerseyResourceEnhancerTest.oauthHelper.tokenValidator } + .to(TokenValidator::class.java) + } +} \ No newline at end of file diff --git a/src/test/kotlin/org/radarbase/jersey/mock/resource/MockResource.kt b/src/test/kotlin/org/radarbase/jersey/mock/resource/MockResource.kt index af980cb..c3658c6 100644 --- a/src/test/kotlin/org/radarbase/jersey/mock/resource/MockResource.kt +++ b/src/test/kotlin/org/radarbase/jersey/mock/resource/MockResource.kt @@ -9,9 +9,9 @@ package org.radarbase.jersey.mock.resource -import org.radarbase.auth.jersey.Auth -import org.radarbase.auth.jersey.Authenticated -import org.radarbase.auth.jersey.NeedsPermission +import org.radarbase.jersey.auth.Auth +import org.radarbase.jersey.auth.Authenticated +import org.radarbase.jersey.auth.NeedsPermission import org.radarcns.auth.authorization.Permission import javax.annotation.Resource import javax.ws.rs.* From 0e9f9c5793b98fbd0f29b2a29ce1712dcbd5afe1 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 7 Oct 2019 10:13:00 +0200 Subject: [PATCH 06/13] Cleaned up imports --- src/main/kotlin/org/radarbase/jersey/auth/Auth.kt | 1 - .../radarbase/jersey/config/RadarJerseyResourceEnhancer.kt | 4 +--- .../radarbase/jersey/exception/HttpApplicationException.kt | 1 - .../jersey/exception/mapper/HttpApplicationExceptionMapper.kt | 2 +- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/org/radarbase/jersey/auth/Auth.kt b/src/main/kotlin/org/radarbase/jersey/auth/Auth.kt index 731b648..2ef68ab 100644 --- a/src/main/kotlin/org/radarbase/jersey/auth/Auth.kt +++ b/src/main/kotlin/org/radarbase/jersey/auth/Auth.kt @@ -14,7 +14,6 @@ import org.radarbase.jersey.exception.HttpBadRequestException import org.radarbase.jersey.exception.HttpForbiddenException import org.radarcns.auth.authorization.Permission import org.radarcns.auth.token.RadarToken -import javax.ws.rs.core.Response interface Auth { /** ID of the OAuth client. */ diff --git a/src/main/kotlin/org/radarbase/jersey/config/RadarJerseyResourceEnhancer.kt b/src/main/kotlin/org/radarbase/jersey/config/RadarJerseyResourceEnhancer.kt index a7e0061..34c8493 100644 --- a/src/main/kotlin/org/radarbase/jersey/config/RadarJerseyResourceEnhancer.kt +++ b/src/main/kotlin/org/radarbase/jersey/config/RadarJerseyResourceEnhancer.kt @@ -14,11 +14,9 @@ import org.glassfish.jersey.process.internal.RequestScoped import org.glassfish.jersey.server.ResourceConfig import org.radarbase.jersey.auth.Auth import org.radarbase.jersey.auth.AuthConfig -import org.radarbase.jersey.auth.jwt.AuthFactory import org.radarbase.jersey.auth.filter.AuthenticationFilter import org.radarbase.jersey.auth.filter.AuthorizationFeature -import kotlin.apply -import kotlin.jvm.java +import org.radarbase.jersey.auth.jwt.AuthFactory /** * Add RADAR auth to a Jersey project. This requires a {@link ProjectService} implementation to be diff --git a/src/main/kotlin/org/radarbase/jersey/exception/HttpApplicationException.kt b/src/main/kotlin/org/radarbase/jersey/exception/HttpApplicationException.kt index eeaf316..2f29f8b 100644 --- a/src/main/kotlin/org/radarbase/jersey/exception/HttpApplicationException.kt +++ b/src/main/kotlin/org/radarbase/jersey/exception/HttpApplicationException.kt @@ -9,7 +9,6 @@ package org.radarbase.jersey.exception -import java.lang.RuntimeException import javax.ws.rs.core.Response open class HttpApplicationException(val status: Int, val code: String, val detailedMessage: String? = null, val additionalHeaders: List> = listOf()) : RuntimeException("[$status] $code: ${detailedMessage ?: "no message"}") { diff --git a/src/main/kotlin/org/radarbase/jersey/exception/mapper/HttpApplicationExceptionMapper.kt b/src/main/kotlin/org/radarbase/jersey/exception/mapper/HttpApplicationExceptionMapper.kt index 7d63b22..3e68c9c 100644 --- a/src/main/kotlin/org/radarbase/jersey/exception/mapper/HttpApplicationExceptionMapper.kt +++ b/src/main/kotlin/org/radarbase/jersey/exception/mapper/HttpApplicationExceptionMapper.kt @@ -10,8 +10,8 @@ package org.radarbase.jersey.inject import com.fasterxml.jackson.core.util.BufferRecyclers -import org.radarbase.jersey.exception.mapper.ExceptionHtmlRenderer import org.radarbase.jersey.exception.HttpApplicationException +import org.radarbase.jersey.exception.mapper.ExceptionHtmlRenderer import org.slf4j.LoggerFactory import javax.inject.Singleton import javax.ws.rs.container.ContainerRequestContext From a16ba9bece50799e32809dd3ca41c78b1fa01593 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 7 Oct 2019 10:53:37 +0200 Subject: [PATCH 07/13] Add config loading --- build.gradle | 31 ++++++++------ .../radarbase/jersey/config/ConfigLoader.kt | 41 +++++++++++++++++++ .../jersey/config/EnhancerFactory.kt | 5 +++ .../auth/RadarJerseyResourceEnhancerTest.kt | 11 ++--- .../mock/MockResourceEnhancerFactory.kt | 13 ++++++ 5 files changed, 81 insertions(+), 20 deletions(-) create mode 100644 src/main/kotlin/org/radarbase/jersey/config/ConfigLoader.kt create mode 100644 src/main/kotlin/org/radarbase/jersey/config/EnhancerFactory.kt create mode 100644 src/test/kotlin/org/radarbase/jersey/mock/MockResourceEnhancerFactory.kt diff --git a/build.gradle b/build.gradle index 1532484..a06510b 100644 --- a/build.gradle +++ b/build.gradle @@ -39,27 +39,34 @@ dependencies { api("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion") + implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jacksonVersion") + // exception template rendering implementation 'com.github.spullara.mustache.java:compiler:0.9.6' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "org.slf4j:slf4j-api:1.7.26" - testImplementation("org.junit.jupiter:junit-jupiter:5.5.2") - testImplementation 'org.hamcrest:hamcrest-all:1.3' - testImplementation("com.squareup.okhttp3:okhttp:$okhttpVersion") + runtimeOnly("org.glassfish.jersey.inject:jersey-hk2:$jerseyVersion") + runtimeOnly("org.glassfish.jersey.media:jersey-media-json-jackson:$jerseyVersion") + + runtimeOnly("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion") + runtimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonVersion") + runtimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:$jacksonVersion") - testImplementation("org.glassfish.grizzly:grizzly-http-server:${grizzlyVersion}") + runtimeOnly 'javax.xml.bind:jaxb-api:2.2.11' + runtimeOnly 'com.sun.xml.bind:jaxb-core:2.2.11' + runtimeOnly 'com.sun.xml.bind:jaxb-impl:2.2.11' + runtimeOnly 'javax.activation:activation:1.1.1' - testImplementation("org.glassfish.jersey.containers:jersey-container-grizzly2-http:${jerseyVersion}") - testImplementation("org.glassfish.jersey.containers:jersey-container-grizzly2-servlet:${jerseyVersion}") - testImplementation("org.glassfish.jersey.inject:jersey-hk2:${jerseyVersion}") - testRuntimeOnly("org.glassfish.jersey.media:jersey-media-json-jackson:${jerseyVersion}") + testImplementation("org.glassfish.jersey.containers:jersey-container-grizzly2-http:$jerseyVersion") - testRuntimeOnly 'javax.xml.bind:jaxb-api:2.2.11' - testRuntimeOnly 'com.sun.xml.bind:jaxb-core:2.2.11' - testRuntimeOnly 'com.sun.xml.bind:jaxb-impl:2.2.11' - testRuntimeOnly 'javax.activation:activation:1.1.1' + testRuntimeOnly("org.glassfish.grizzly:grizzly-http-server:$grizzlyVersion") + testRuntimeOnly("org.glassfish.jersey.containers:jersey-container-grizzly2-servlet:$jerseyVersion") + + testImplementation("org.junit.jupiter:junit-jupiter:5.5.2") + testImplementation 'org.hamcrest:hamcrest-all:1.3' + testImplementation("com.squareup.okhttp3:okhttp:$okhttpVersion") testRuntime 'org.slf4j:slf4j-simple:1.7.26' } diff --git a/src/main/kotlin/org/radarbase/jersey/config/ConfigLoader.kt b/src/main/kotlin/org/radarbase/jersey/config/ConfigLoader.kt new file mode 100644 index 0000000..72ba6c9 --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/config/ConfigLoader.kt @@ -0,0 +1,41 @@ +package org.radarbase.jersey.config + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory +import org.glassfish.jersey.server.ResourceConfig +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.io.File +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Paths +import kotlin.system.exitProcess + +object ConfigLoader { + inline fun loadResources(factoryClass: Class<*>, config: T): ResourceConfig { + val enhancerFactory = factoryClass.getConstructor(T::class.java).newInstance(config) as EnhancerFactory + return RadarResourceConfigFactory().resources(enhancerFactory.createEnhancers()) + } + + inline fun loadConfig(fileName: String, args: Array): T { + val configFileName = when { + args.size == 1 -> args[0] + Files.exists(Paths.get(fileName)) -> fileName + else -> null + } + checkNotNull(configFileName) { "Configuration not provided." } + + val configFile = File(configFileName) + logger.info("Reading configuration from ${configFile.absolutePath}") + try { + val mapper = ObjectMapper(YAMLFactory()) + return mapper.readValue(configFile, T::class.java) + } catch (ex: IOException) { + logger.error("Usage: [$fileName]") + logger.error("Failed to read config file $configFile: ${ex.message}") + exitProcess(1) + } + } + + val logger: Logger = LoggerFactory.getLogger(ConfigLoader::class.java) +} diff --git a/src/main/kotlin/org/radarbase/jersey/config/EnhancerFactory.kt b/src/main/kotlin/org/radarbase/jersey/config/EnhancerFactory.kt new file mode 100644 index 0000000..f11af6e --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/config/EnhancerFactory.kt @@ -0,0 +1,5 @@ +package org.radarbase.jersey.config + +interface EnhancerFactory { + fun createEnhancers(): List +} \ No newline at end of file diff --git a/src/test/kotlin/org/radarbase/jersey/auth/RadarJerseyResourceEnhancerTest.kt b/src/test/kotlin/org/radarbase/jersey/auth/RadarJerseyResourceEnhancerTest.kt index 7195c61..0a1f761 100644 --- a/src/test/kotlin/org/radarbase/jersey/auth/RadarJerseyResourceEnhancerTest.kt +++ b/src/test/kotlin/org/radarbase/jersey/auth/RadarJerseyResourceEnhancerTest.kt @@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test import org.radarbase.jersey.auth.OAuthHelper.Companion.bearerHeader import org.radarbase.jersey.config.* import org.radarbase.jersey.mock.MockResourceEnhancer +import org.radarbase.jersey.mock.MockResourceEnhancerFactory import java.net.URI internal class RadarJerseyResourceEnhancerTest { @@ -34,15 +35,9 @@ internal class RadarJerseyResourceEnhancerTest { managementPortalUrl = "http://localhost:8080", jwtResourceName = "res_ManagementPortal") - val enhancers = listOf( - MockResourceEnhancer(), - RadarJerseyResourceEnhancer(authConfig), - ManagementPortalResourceEnhancer(), - HttpExceptionResourceEnhancer(), - GeneralExceptionResourceEnhancer()) + val resources = ConfigLoader.loadResources(MockResourceEnhancerFactory::class.java, authConfig) - val resourceConfig = RadarResourceConfigFactory().resources(enhancers) - server = GrizzlyHttpServerFactory.createHttpServer(URI.create("http://localhost:9091"), resourceConfig) + server = GrizzlyHttpServerFactory.createHttpServer(URI.create("http://localhost:9091"), resources) server.start() client = OkHttpClient() diff --git a/src/test/kotlin/org/radarbase/jersey/mock/MockResourceEnhancerFactory.kt b/src/test/kotlin/org/radarbase/jersey/mock/MockResourceEnhancerFactory.kt new file mode 100644 index 0000000..4f5d7ef --- /dev/null +++ b/src/test/kotlin/org/radarbase/jersey/mock/MockResourceEnhancerFactory.kt @@ -0,0 +1,13 @@ +package org.radarbase.jersey.mock + +import org.radarbase.jersey.auth.AuthConfig +import org.radarbase.jersey.config.* + +class MockResourceEnhancerFactory(private val config: AuthConfig) : EnhancerFactory { + override fun createEnhancers(): List = listOf( + MockResourceEnhancer(), + RadarJerseyResourceEnhancer(config), + ManagementPortalResourceEnhancer(), + HttpExceptionResourceEnhancer(), + GeneralExceptionResourceEnhancer()) +} \ No newline at end of file From 4119082da7d5b242319ef2ac95b6a7e1ec483151 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 7 Oct 2019 11:04:11 +0200 Subject: [PATCH 08/13] Add grizzly server --- build.gradle | 3 +- .../org/radarbase/jersey/GrizzlyServer.kt | 45 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/org/radarbase/jersey/GrizzlyServer.kt diff --git a/build.gradle b/build.gradle index a06510b..248655d 100644 --- a/build.gradle +++ b/build.gradle @@ -39,6 +39,7 @@ dependencies { api("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion") + implementation("org.glassfish.jersey.containers:jersey-container-grizzly2-http:$jerseyVersion") implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jacksonVersion") // exception template rendering @@ -59,8 +60,6 @@ dependencies { runtimeOnly 'com.sun.xml.bind:jaxb-impl:2.2.11' runtimeOnly 'javax.activation:activation:1.1.1' - testImplementation("org.glassfish.jersey.containers:jersey-container-grizzly2-http:$jerseyVersion") - testRuntimeOnly("org.glassfish.grizzly:grizzly-http-server:$grizzlyVersion") testRuntimeOnly("org.glassfish.jersey.containers:jersey-container-grizzly2-servlet:$jerseyVersion") diff --git a/src/main/kotlin/org/radarbase/jersey/GrizzlyServer.kt b/src/main/kotlin/org/radarbase/jersey/GrizzlyServer.kt new file mode 100644 index 0000000..6e77149 --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/GrizzlyServer.kt @@ -0,0 +1,45 @@ +package org.radarbase.jersey + +import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory +import org.glassfish.jersey.server.ResourceConfig +import org.slf4j.LoggerFactory +import java.lang.IllegalStateException +import java.net.URI + +class GrizzlyServer(private val baseUri: URI, resources: ResourceConfig) { + private val server = GrizzlyHttpServerFactory.createHttpServer(baseUri, resources) + + private val shutdownHook = Thread(Runnable { + logger.info("Stopping server..") + server.shutdown() + }, "shutdownHook") + + + fun listen() { + // register shutdown hook + Runtime.getRuntime().addShutdownHook(shutdownHook) + + try { + server.start() + + logger.info(String.format("Jersey app started on %s.\nPress Ctrl+C to exit...", + baseUri)) + Thread.currentThread().join() + } catch (e: Exception) { + logger.error("Error starting server: {}", e.toString()) + } + } + + fun shutdown() { + try { + Runtime.getRuntime().removeShutdownHook(shutdownHook) + } catch (ex: IllegalStateException) { + // ignore + } + server.shutdown() + } + + companion object { + private val logger = LoggerFactory.getLogger(GrizzlyServer::class.java) + } +} From ddcf7b3251d3e4cb9aef3aa041b443e4b8087487 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 7 Oct 2019 13:43:35 +0200 Subject: [PATCH 09/13] Support Grizzly JMX and start method --- src/main/kotlin/org/radarbase/jersey/GrizzlyServer.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/org/radarbase/jersey/GrizzlyServer.kt b/src/main/kotlin/org/radarbase/jersey/GrizzlyServer.kt index 6e77149..f293d34 100644 --- a/src/main/kotlin/org/radarbase/jersey/GrizzlyServer.kt +++ b/src/main/kotlin/org/radarbase/jersey/GrizzlyServer.kt @@ -6,14 +6,18 @@ import org.slf4j.LoggerFactory import java.lang.IllegalStateException import java.net.URI -class GrizzlyServer(private val baseUri: URI, resources: ResourceConfig) { +class GrizzlyServer(private val baseUri: URI, resources: ResourceConfig, enableJmx: Boolean = false) { private val server = GrizzlyHttpServerFactory.createHttpServer(baseUri, resources) + .also { it.serverConfiguration.isJmxEnabled = enableJmx } private val shutdownHook = Thread(Runnable { logger.info("Stopping server..") server.shutdown() }, "shutdownHook") + fun start() { + server.start() + } fun listen() { // register shutdown hook From 25d17798ea6b1a3e12dbc8aa6d7e91fdd73761d6 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 7 Oct 2019 13:44:17 +0200 Subject: [PATCH 10/13] Extend documentation --- README.md | 50 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 26a1464..61d24fb 100644 --- a/README.md +++ b/README.md @@ -46,12 +46,28 @@ class Users( } ``` -These APIs are activated by adding `JerseyResourceEnhancer` implementations to your resource definition: +These APIs are activated by adding an `EnhancerFactory` implementation to your resource definition: ```kotlin -val resourceConfig = RadarResourceConfigFactory().resources(listOf( - // My own resource configuration - object : JerseyResourceEnhancer { +class MyEnhancerFactory(private val config: MyConfigClass): EnhancerFactory { + override fun createEnhancers() = listOf( + // My own resource configuration + MyResourceEnhancer(), + // RADAR OAuth2 enhancement + RadarJerseyResourceEnhancer(AuthConfig( + managementPortalUrl = "http://...", + jwtResourceName = "res_MyResource")), + // Use ManagementPortal OAuth implementation + ManagementPortalResourceEnhancer(), + // HttpApplicationException handling + HttpExceptionResourceEnhancer(), + // General error handling (WebApplicationException and any other Exception) + GeneralExceptionResourceEnhancer()) + + class MyResourceEnhancer: JerseyResourceEnhancer { override fun enhanceBinder(binder: AbstractBinder) { + binder.bind(config) + .to(MyConfigClass::class.java) + binder.bind(MyProjectService::class.java) .to(ProjectService::class.java) .`in`(Singleton::class.java) @@ -61,23 +77,23 @@ val resourceConfig = RadarResourceConfigFactory().resources(listOf( .`in`(Singleton::class.java) } } - // RADAR OAuth2 enhancement - RadarJerseyResourceEnhancer(AuthConfig( - managementPortalUrl = "http://...", - jwtResourceName = "res_MyResource")), - // Use ManagementPortal OAuth implementation - ManagementPortalResourceEnhancer(), - // HttpApplicationException handling - HttpExceptionResourceEnhancer(), - // General error handling (WebApplicationException and any other Exception) - GeneralExceptionResourceEnhancer() -)) +} ``` - Ensure that a class implementing `org.radarbase.jersey.auth.ProjectService` is added to the binder. +This factory can then be specified in your main method, by adding it to your `MyConfigClass` definition: +```kotlin +fun main(args: Array) { + val config: MyConfigClass = ConfigLoader.loadConfig("my-config-name.yml", args) + val resources = ConfigLoader.loadResources(config.resourceConfig, config) + val server = GrizzlyServer(config.baseUri, resources, config.isJmxEnabled) + // Listen until JVM shutdown + server.listen() +} +``` + ## Error handling -This package adds some error handling. Specifically, `org.radarbase.auth.jersey.exception.HttpApplicationException` can be used and extended to serve detailed error messages with customized logging and HTML templating. They can be thrown from any resource. +This package adds some error handling. Specifically, `org.radarbase.jersey.exception.HttpApplicationException` and its subclasses can be used and extended to serve detailed error messages with customized logging and HTML templating. They can be thrown from any resource. To serve custom HTML error messages for error codes 400 to 599, add a Mustache template to the classpath in directory `org/radarbase/jersey/exception/mapper/.html`. You can use special cases `4xx.html` and `5xx.html` as a catch-all template. The templates can use variables `status` for the HTTP status code, `code` for short-hand code for the specific error, and an optional `detailedMessage` for a human-readable message. From 4ee834a64d242f7d65e4b29a8a5e0dbf2c01681e Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 7 Oct 2019 15:31:02 +0200 Subject: [PATCH 11/13] Updated exception rendering --- build.gradle | 3 +- .../config/HttpExceptionResourceEnhancer.kt | 23 ++++++++++--- .../mapper/DefaultJsonExceptionRenderer.kt | 28 ++++++++++++++++ .../mapper/DefaultTextExceptionRenderer.kt | 21 ++++++++++++ ...onHtmlRenderer.kt => ExceptionRenderer.kt} | 2 +- ...er.kt => HtmlTemplateExceptionRenderer.kt} | 7 ++-- .../mapper/HttpApplicationExceptionMapper.kt | 32 ++++++++----------- 7 files changed, 90 insertions(+), 26 deletions(-) create mode 100644 src/main/kotlin/org/radarbase/jersey/exception/mapper/DefaultJsonExceptionRenderer.kt create mode 100644 src/main/kotlin/org/radarbase/jersey/exception/mapper/DefaultTextExceptionRenderer.kt rename src/main/kotlin/org/radarbase/jersey/exception/mapper/{ExceptionHtmlRenderer.kt => ExceptionRenderer.kt} (92%) rename src/main/kotlin/org/radarbase/jersey/exception/mapper/{DefaultExceptionHtmlRenderer.kt => HtmlTemplateExceptionRenderer.kt} (91%) diff --git a/build.gradle b/build.gradle index 248655d..8cf9740 100644 --- a/build.gradle +++ b/build.gradle @@ -48,7 +48,8 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "org.slf4j:slf4j-api:1.7.26" - runtimeOnly("org.glassfish.jersey.inject:jersey-hk2:$jerseyVersion") + implementation("org.glassfish.jersey.inject:jersey-hk2:$jerseyVersion") + runtimeOnly("org.glassfish.jersey.media:jersey-media-json-jackson:$jerseyVersion") runtimeOnly("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion") diff --git a/src/main/kotlin/org/radarbase/jersey/config/HttpExceptionResourceEnhancer.kt b/src/main/kotlin/org/radarbase/jersey/config/HttpExceptionResourceEnhancer.kt index f230ef7..879335a 100644 --- a/src/main/kotlin/org/radarbase/jersey/config/HttpExceptionResourceEnhancer.kt +++ b/src/main/kotlin/org/radarbase/jersey/config/HttpExceptionResourceEnhancer.kt @@ -3,15 +3,30 @@ package org.radarbase.jersey.config import org.glassfish.jersey.internal.inject.AbstractBinder import org.glassfish.jersey.internal.inject.PerThread import org.glassfish.jersey.server.ResourceConfig -import org.radarbase.jersey.exception.mapper.DefaultExceptionHtmlRenderer -import org.radarbase.jersey.exception.mapper.ExceptionHtmlRenderer +import org.radarbase.jersey.exception.mapper.DefaultJsonExceptionRenderer +import org.radarbase.jersey.exception.mapper.DefaultTextExceptionRenderer +import org.radarbase.jersey.exception.mapper.HtmlTemplateExceptionRenderer +import org.radarbase.jersey.exception.mapper.ExceptionRenderer import org.radarbase.jersey.inject.HttpApplicationExceptionMapper +import javax.inject.Singleton +/** Add HttpApplicationException handling. This includes a HTML templating solution. */ class HttpExceptionResourceEnhancer: JerseyResourceEnhancer { override fun enhanceBinder(binder: AbstractBinder) { - binder.bind(DefaultExceptionHtmlRenderer::class.java) - .to(ExceptionHtmlRenderer::class.java) + binder.bind(HtmlTemplateExceptionRenderer::class.java) + .to(ExceptionRenderer::class.java) + .named("text/html") .`in`(PerThread::class.java) + + binder.bind(DefaultJsonExceptionRenderer::class.java) + .to(ExceptionRenderer::class.java) + .named("application/json") + .`in`(Singleton::class.java) + + binder.bind(DefaultTextExceptionRenderer::class.java) + .to(ExceptionRenderer::class.java) + .named("text/plain") + .`in`(Singleton::class.java) } override fun enhanceResources(resourceConfig: ResourceConfig) { diff --git a/src/main/kotlin/org/radarbase/jersey/exception/mapper/DefaultJsonExceptionRenderer.kt b/src/main/kotlin/org/radarbase/jersey/exception/mapper/DefaultJsonExceptionRenderer.kt new file mode 100644 index 0000000..6eb05b3 --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/exception/mapper/DefaultJsonExceptionRenderer.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019. The Hyve + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * See the file LICENSE in the root of this repository. + */ + +package org.radarbase.jersey.exception.mapper + +import com.fasterxml.jackson.core.util.BufferRecyclers +import org.radarbase.jersey.exception.HttpApplicationException + +/** + * Render an exception using a Mustache HTML document. + */ +class DefaultJsonExceptionRenderer: ExceptionRenderer { + override fun render(exception: HttpApplicationException): String { + val stringEncoder = BufferRecyclers.getJsonStringEncoder() + val quotedError = stringEncoder.quoteAsUTF8(exception.code).toString(Charsets.UTF_8) + val quotedDescription = exception.detailedMessage?.let { + '"' + stringEncoder.quoteAsUTF8(it).toString(Charsets.UTF_8) + '"' + } ?: "null" + + return "{\"error\":\"$quotedError\",\"error_description\":$quotedDescription}" + } +} diff --git a/src/main/kotlin/org/radarbase/jersey/exception/mapper/DefaultTextExceptionRenderer.kt b/src/main/kotlin/org/radarbase/jersey/exception/mapper/DefaultTextExceptionRenderer.kt new file mode 100644 index 0000000..849f142 --- /dev/null +++ b/src/main/kotlin/org/radarbase/jersey/exception/mapper/DefaultTextExceptionRenderer.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2019. The Hyve + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * See the file LICENSE in the root of this repository. + */ + +package org.radarbase.jersey.exception.mapper + +import org.radarbase.jersey.exception.HttpApplicationException + +/** + * Render an exception using a Mustache HTML document. + */ +class DefaultTextExceptionRenderer: ExceptionRenderer { + override fun render(exception: HttpApplicationException): String { + return "[${exception.status}] ${exception.code}: ${exception.detailedMessage ?: "unknown reason"}" + } +} diff --git a/src/main/kotlin/org/radarbase/jersey/exception/mapper/ExceptionHtmlRenderer.kt b/src/main/kotlin/org/radarbase/jersey/exception/mapper/ExceptionRenderer.kt similarity index 92% rename from src/main/kotlin/org/radarbase/jersey/exception/mapper/ExceptionHtmlRenderer.kt rename to src/main/kotlin/org/radarbase/jersey/exception/mapper/ExceptionRenderer.kt index 2bee946..67cdc2a 100644 --- a/src/main/kotlin/org/radarbase/jersey/exception/mapper/ExceptionHtmlRenderer.kt +++ b/src/main/kotlin/org/radarbase/jersey/exception/mapper/ExceptionRenderer.kt @@ -11,6 +11,6 @@ package org.radarbase.jersey.exception.mapper import org.radarbase.jersey.exception.HttpApplicationException -interface ExceptionHtmlRenderer { +interface ExceptionRenderer { fun render(exception: HttpApplicationException): String } diff --git a/src/main/kotlin/org/radarbase/jersey/exception/mapper/DefaultExceptionHtmlRenderer.kt b/src/main/kotlin/org/radarbase/jersey/exception/mapper/HtmlTemplateExceptionRenderer.kt similarity index 91% rename from src/main/kotlin/org/radarbase/jersey/exception/mapper/DefaultExceptionHtmlRenderer.kt rename to src/main/kotlin/org/radarbase/jersey/exception/mapper/HtmlTemplateExceptionRenderer.kt index d482778..ff9d53b 100644 --- a/src/main/kotlin/org/radarbase/jersey/exception/mapper/DefaultExceptionHtmlRenderer.kt +++ b/src/main/kotlin/org/radarbase/jersey/exception/mapper/HtmlTemplateExceptionRenderer.kt @@ -17,7 +17,10 @@ import java.io.ByteArrayOutputStream import java.io.IOException import java.io.OutputStreamWriter -class DefaultExceptionHtmlRenderer: ExceptionHtmlRenderer { +/** + * Render an exception using a Mustache HTML document. + */ +class HtmlTemplateExceptionRenderer: ExceptionRenderer { private val errorTemplates: Map private val template4xx: Mustache @@ -67,6 +70,6 @@ class DefaultExceptionHtmlRenderer: ExceptionHtmlRenderer { } companion object { - private val logger = LoggerFactory.getLogger(DefaultExceptionHtmlRenderer::class.java) + private val logger = LoggerFactory.getLogger(HtmlTemplateExceptionRenderer::class.java) } } diff --git a/src/main/kotlin/org/radarbase/jersey/exception/mapper/HttpApplicationExceptionMapper.kt b/src/main/kotlin/org/radarbase/jersey/exception/mapper/HttpApplicationExceptionMapper.kt index 3e68c9c..074aa91 100644 --- a/src/main/kotlin/org/radarbase/jersey/exception/mapper/HttpApplicationExceptionMapper.kt +++ b/src/main/kotlin/org/radarbase/jersey/exception/mapper/HttpApplicationExceptionMapper.kt @@ -10,8 +10,9 @@ package org.radarbase.jersey.inject import com.fasterxml.jackson.core.util.BufferRecyclers +import org.glassfish.hk2.api.IterableProvider import org.radarbase.jersey.exception.HttpApplicationException -import org.radarbase.jersey.exception.mapper.ExceptionHtmlRenderer +import org.radarbase.jersey.exception.mapper.ExceptionRenderer import org.slf4j.LoggerFactory import javax.inject.Singleton import javax.ws.rs.container.ContainerRequestContext @@ -28,32 +29,25 @@ import kotlin.text.Charsets.UTF_8 class HttpApplicationExceptionMapper( @Context private val uriInfo: UriInfo, @Context private val requestContext: ContainerRequestContext, - @Context private val htmlRenderer: ExceptionHtmlRenderer + @Context private val renderers: IterableProvider ) : ExceptionMapper { override fun toResponse(exception: HttpApplicationException): Response { - var mediaType = requestContext.acceptableMediaTypes - .firstOrNull { type -> type in setOf(MediaType.WILDCARD_TYPE, MediaType.APPLICATION_JSON_TYPE, MediaType.TEXT_HTML_TYPE, MediaType.TEXT_PLAIN) } + val mediaType = requestContext.acceptableMediaTypes + .firstOrNull { type -> type in supportedTypes } + .takeIf { it != MediaType.WILDCARD_TYPE } ?: MediaType.TEXT_PLAIN_TYPE logger.error("[{}] {} <{}> - {}: {}", exception.status, uriInfo.absolutePath, mediaType, exception.code, exception.detailedMessage) - val entity = when (mediaType) { - MediaType.APPLICATION_JSON_TYPE -> { - val stringEncoder = BufferRecyclers.getJsonStringEncoder() - val quotedError = stringEncoder.quoteAsUTF8(exception.code).toString(UTF_8) - val quotedDescription = exception.detailedMessage?.let { - '"' + stringEncoder.quoteAsUTF8(it).toString(UTF_8) + '"' - } ?: "null" + val renderer = renderers.named(mediaType.toString()).firstOrNull() - "{\"error\":\"$quotedError\",\"error_description\":$quotedDescription}" - } - MediaType.TEXT_HTML_TYPE -> htmlRenderer.render(exception) - else -> { - mediaType = MediaType.TEXT_PLAIN_TYPE - "[${exception.status}] ${exception.code}: ${exception.detailedMessage ?: "unknown reason"}" - } + if (renderer == null) { + logger.error("Cannot render exception with type {}: no renderer registered", mediaType) + return Response.status(exception.status).build() } + val entity = renderer.render(exception) + val responseBuilder = Response.status(exception.status) .entity(entity) .header("Content-Type", mediaType.withCharset("utf-8").toString()) @@ -67,5 +61,7 @@ class HttpApplicationExceptionMapper( companion object { private val logger = LoggerFactory.getLogger(HttpApplicationExceptionMapper::class.java) + + private val supportedTypes = setOf(MediaType.WILDCARD_TYPE, MediaType.APPLICATION_JSON_TYPE, MediaType.TEXT_HTML_TYPE, MediaType.TEXT_PLAIN_TYPE) } } From b9f2496fb0b022efc78d841af75e8aa13388e1c1 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 7 Oct 2019 15:31:26 +0200 Subject: [PATCH 12/13] Add documentation and remove unused classes --- build.gradle | 2 + .../org/radarbase/jersey/GrizzlyServer.kt | 25 ++++++++++- .../config/ApplicationResourceConfig.kt | 10 ----- .../radarbase/jersey/config/ConfigLoader.kt | 44 +++++++++++++++++-- .../jersey/config/EnhancerFactory.kt | 3 ++ .../GeneralExceptionResourceEnhancer.kt | 1 + .../jersey/config/JerseyResourceEnhancer.kt | 12 +++++ .../ManagementPortalResourceEnhancer.kt | 5 +-- .../config/RadarResourceConfigFactory.kt | 19 -------- 9 files changed, 83 insertions(+), 38 deletions(-) delete mode 100644 src/main/kotlin/org/radarbase/jersey/config/ApplicationResourceConfig.kt delete mode 100644 src/main/kotlin/org/radarbase/jersey/config/RadarResourceConfigFactory.kt diff --git a/build.gradle b/build.gradle index 8cf9740..973cc72 100644 --- a/build.gradle +++ b/build.gradle @@ -79,6 +79,8 @@ test { useJUnitPlatform() testLogging { events "passed", "skipped", "failed" + exceptionFormat "full" + showStandardStreams = true } } diff --git a/src/main/kotlin/org/radarbase/jersey/GrizzlyServer.kt b/src/main/kotlin/org/radarbase/jersey/GrizzlyServer.kt index f293d34..0217823 100644 --- a/src/main/kotlin/org/radarbase/jersey/GrizzlyServer.kt +++ b/src/main/kotlin/org/radarbase/jersey/GrizzlyServer.kt @@ -6,19 +6,37 @@ import org.slf4j.LoggerFactory import java.lang.IllegalStateException import java.net.URI -class GrizzlyServer(private val baseUri: URI, resources: ResourceConfig, enableJmx: Boolean = false) { +/** + * Grizzly server wrapper. + */ +class GrizzlyServer( + /** Base URI for the server to listen at. */ + private val baseUri: URI, + /** ResourceConfig including all needed Jersey resources. */ + resources: ResourceConfig, + /** + * Whether to enable JMX. If true, ensure that additional JMX dependencies from Grizzly + * are imported. + */ + enableJmx: Boolean = false) { private val server = GrizzlyHttpServerFactory.createHttpServer(baseUri, resources) .also { it.serverConfiguration.isJmxEnabled = enableJmx } private val shutdownHook = Thread(Runnable { - logger.info("Stopping server..") + logger.info("Stopping HTTP server...") server.shutdown() }, "shutdownHook") + /** Start the server. This is a non-blocking call. */ fun start() { server.start() } + /** + * Listen for connections indefinitely. This adds a shutdown hook to stop the server + * once the JVM is shut down. Otherwise, this can be interrupted with an + * InterruptedException. If an error occurs, {@link #shutdown()} should still be called. + */ fun listen() { // register shutdown hook Runtime.getRuntime().addShutdownHook(shutdownHook) @@ -34,6 +52,9 @@ class GrizzlyServer(private val baseUri: URI, resources: ResourceConfig, enableJ } } + /** + * Stop the HTTP server. + */ fun shutdown() { try { Runtime.getRuntime().removeShutdownHook(shutdownHook) diff --git a/src/main/kotlin/org/radarbase/jersey/config/ApplicationResourceConfig.kt b/src/main/kotlin/org/radarbase/jersey/config/ApplicationResourceConfig.kt deleted file mode 100644 index 7947399..0000000 --- a/src/main/kotlin/org/radarbase/jersey/config/ApplicationResourceConfig.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.radarbase.jersey.config - -import org.glassfish.jersey.internal.inject.AbstractBinder -import org.glassfish.jersey.server.ResourceConfig - -interface ApplicationResourceConfig { - fun createEnhancers(): List - fun configureResources(resources: ResourceConfig) - fun configureBinder(binder: AbstractBinder) = Unit -} \ No newline at end of file diff --git a/src/main/kotlin/org/radarbase/jersey/config/ConfigLoader.kt b/src/main/kotlin/org/radarbase/jersey/config/ConfigLoader.kt index 72ba6c9..2e0f708 100644 --- a/src/main/kotlin/org/radarbase/jersey/config/ConfigLoader.kt +++ b/src/main/kotlin/org/radarbase/jersey/config/ConfigLoader.kt @@ -2,6 +2,7 @@ package org.radarbase.jersey.config import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.dataformat.yaml.YAMLFactory +import org.glassfish.jersey.internal.inject.AbstractBinder import org.glassfish.jersey.server.ResourceConfig import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -12,18 +13,36 @@ import java.nio.file.Paths import kotlin.system.exitProcess object ConfigLoader { - inline fun loadResources(factoryClass: Class<*>, config: T): ResourceConfig { - val enhancerFactory = factoryClass.getConstructor(T::class.java).newInstance(config) as EnhancerFactory - return RadarResourceConfigFactory().resources(enhancerFactory.createEnhancers()) + /** + * Load resources using a EnhancerFactory. + * + * @param factoryClass: EnhancerFactory implementation class. + * @param parameters: parameters that should be passed to the EnhancerFactory constructor. + * Ensure that all factories in your project have a same constructor signature for this to + * work. + * @throws NoSuchMethodException if the constructor cannot be found + * @throws ReflectiveOperationException if the class cannot be instantiated + */ + fun loadResources(factoryClass: Class, vararg parameters: Any): ResourceConfig { + val parameterClasses = parameters.map { it.javaClass }.toTypedArray() + val enhancerFactory = factoryClass.getConstructor(*parameterClasses) + .newInstance(*parameters) + return createResourceConfig(enhancerFactory.createEnhancers()) } + /** + * Load a configuration from YAML file. The filename is searched in the current working + * directory. This exits with a usage information message if the file cannot be loaded. + * + * @throws IllegalArgumentException if a file matching configFileName cannot be found + */ inline fun loadConfig(fileName: String, args: Array): T { val configFileName = when { args.size == 1 -> args[0] Files.exists(Paths.get(fileName)) -> fileName else -> null } - checkNotNull(configFileName) { "Configuration not provided." } + requireNotNull(configFileName) { "Configuration not provided." } val configFile = File(configFileName) logger.info("Reading configuration from ${configFile.absolutePath}") @@ -37,5 +56,22 @@ object ConfigLoader { } } + /** + * Create a resourceConfig based on the provided resource enhancers. This method also disables + * the WADL since it may be identified as a security risk. + */ + fun createResourceConfig(enhancers: List): ResourceConfig { + val resources = ResourceConfig() + resources.property("jersey.config.server.wadl.disableWadl", true) + enhancers.forEach { it.enhanceResources(resources) } + + resources.register(object : AbstractBinder() { + override fun configure() { + enhancers.forEach { it.enhanceBinder(this) } + } + }) + return resources + } + val logger: Logger = LoggerFactory.getLogger(ConfigLoader::class.java) } diff --git a/src/main/kotlin/org/radarbase/jersey/config/EnhancerFactory.kt b/src/main/kotlin/org/radarbase/jersey/config/EnhancerFactory.kt index f11af6e..65b4a67 100644 --- a/src/main/kotlin/org/radarbase/jersey/config/EnhancerFactory.kt +++ b/src/main/kotlin/org/radarbase/jersey/config/EnhancerFactory.kt @@ -1,5 +1,8 @@ package org.radarbase.jersey.config +/** + * Factory to create resource enhancers with. + */ interface EnhancerFactory { fun createEnhancers(): List } \ No newline at end of file diff --git a/src/main/kotlin/org/radarbase/jersey/config/GeneralExceptionResourceEnhancer.kt b/src/main/kotlin/org/radarbase/jersey/config/GeneralExceptionResourceEnhancer.kt index bb8f873..e8a3b6f 100644 --- a/src/main/kotlin/org/radarbase/jersey/config/GeneralExceptionResourceEnhancer.kt +++ b/src/main/kotlin/org/radarbase/jersey/config/GeneralExceptionResourceEnhancer.kt @@ -4,6 +4,7 @@ import org.glassfish.jersey.server.ResourceConfig import org.radarbase.appconfig.exception.UnhandledExceptionMapper import org.radarbase.appconfig.exception.WebApplicationExceptionMapper +/** Add WebApplicationException and any exception handling. */ class GeneralExceptionResourceEnhancer: JerseyResourceEnhancer { override fun enhanceResources(resourceConfig: ResourceConfig) { resourceConfig.registerClasses( diff --git a/src/main/kotlin/org/radarbase/jersey/config/JerseyResourceEnhancer.kt b/src/main/kotlin/org/radarbase/jersey/config/JerseyResourceEnhancer.kt index d583432..c5b4285 100644 --- a/src/main/kotlin/org/radarbase/jersey/config/JerseyResourceEnhancer.kt +++ b/src/main/kotlin/org/radarbase/jersey/config/JerseyResourceEnhancer.kt @@ -12,7 +12,19 @@ package org.radarbase.jersey.config import org.glassfish.jersey.internal.inject.AbstractBinder import org.glassfish.jersey.server.ResourceConfig +/** + * Enhance a Jersey ResourceConfig and a binder. This is used for dependency injection with + * {@code @Context}. + */ interface JerseyResourceEnhancer { + /** + * Enhance the ResourceConfig directly. Use this for classes with Jersey-recognized classes like + * {@code @Resource}, {@code @Provider} or {@code ContextResolver}. + */ fun enhanceResources(resourceConfig: ResourceConfig) = Unit + + /** + * Enhance an AbstractBinder. Use this for app-specific bindings. + */ fun enhanceBinder(binder: AbstractBinder) = Unit } diff --git a/src/main/kotlin/org/radarbase/jersey/config/ManagementPortalResourceEnhancer.kt b/src/main/kotlin/org/radarbase/jersey/config/ManagementPortalResourceEnhancer.kt index a31cc0f..b82c11c 100644 --- a/src/main/kotlin/org/radarbase/jersey/config/ManagementPortalResourceEnhancer.kt +++ b/src/main/kotlin/org/radarbase/jersey/config/ManagementPortalResourceEnhancer.kt @@ -17,9 +17,8 @@ import org.radarcns.auth.authentication.TokenValidator import javax.inject.Singleton /** - * Registration for authorization against a ManagementPortal. - * - * It requires managementPortalUrl and jwtResourceName to be set in the AuthConfig. + * Registration for authorization against a ManagementPortal. It requires managementPortalUrl and + * jwtResourceName to be set in the AuthConfig. */ class ManagementPortalResourceEnhancer : JerseyResourceEnhancer { override fun enhanceBinder(binder: AbstractBinder) { diff --git a/src/main/kotlin/org/radarbase/jersey/config/RadarResourceConfigFactory.kt b/src/main/kotlin/org/radarbase/jersey/config/RadarResourceConfigFactory.kt deleted file mode 100644 index b456ea7..0000000 --- a/src/main/kotlin/org/radarbase/jersey/config/RadarResourceConfigFactory.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.radarbase.jersey.config - -import org.glassfish.jersey.internal.inject.AbstractBinder -import org.glassfish.jersey.server.ResourceConfig - -class RadarResourceConfigFactory { - fun resources(enhancers: List): ResourceConfig { - val resources = ResourceConfig() - resources.property("jersey.config.server.wadl.disableWadl", true) - enhancers.forEach { it.enhanceResources(resources) } - - resources.register(object : AbstractBinder() { - override fun configure() { - enhancers.forEach { it.enhanceBinder(this) } - } - }) - return resources - } -} \ No newline at end of file From ed73c830270e18055fd5c27580b6ee3bb78d7f50 Mon Sep 17 00:00:00 2001 From: Joris Borgdorff Date: Mon, 7 Oct 2019 15:37:15 +0200 Subject: [PATCH 13/13] Bump version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 973cc72..eea7a02 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { description = 'Library for Jersey authorization, exception handling and configuration with the RADAR platform' group = 'org.radarbase' -version = '0.1.1-SNAPSHOT' +version = '0.2.0' ext { githubRepoName = 'RADAR-base/radar-jersey'