forked from cashapp/misk
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[CLOSES cashapp#1129] DashboardTabProvider
* New pattern of multibinding tabs to a DashboardTabProvider along with an AccessAnnotation * Allows for Misk tabs to be set with the authentication from the multibound `AdminDashboardAccess` access annotation * Also allows for service tabs to be easily set with service specific Access Annotations * Fixes small bug in Loader tab where dashboard doesn't load when no tabs are bound
- Loading branch information
Showing
12 changed files
with
208 additions
and
119 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package misk.web | ||
|
||
import misk.security.authz.AccessAnnotationEntry | ||
import javax.inject.Inject | ||
import javax.inject.Provider | ||
import kotlin.reflect.KClass | ||
|
||
class DashboardTab( | ||
slug: String, | ||
url_path_prefix: String, | ||
val name: String, | ||
val category: String = "Admin", | ||
capabilities: Set<String> = setOf(), | ||
services: Set<String> = setOf() | ||
) : WebTab(slug, url_path_prefix, capabilities, services) | ||
|
||
/** | ||
* Sets the tab's authentication based on the injected AdminDashboardAccess access annotation entry | ||
*/ | ||
class DashboardTabProvider( | ||
val slug: String, | ||
val url_path_prefix: String, | ||
val name: String, | ||
val category: String = "Admin", | ||
val accessAnnotation: KClass<out Annotation> | ||
) : Provider<DashboardTab> { | ||
@Inject | ||
lateinit var registeredEntries: List<AccessAnnotationEntry> | ||
|
||
override fun get(): DashboardTab { | ||
val accessAnnotationEntry = registeredEntries.find { it.annotation == accessAnnotation }!! | ||
return DashboardTab( | ||
slug = slug, | ||
url_path_prefix = url_path_prefix, | ||
name = name, | ||
category = category, | ||
capabilities = accessAnnotationEntry.capabilities.toSet(), | ||
services = accessAnnotationEntry.services.toSet() | ||
) | ||
} | ||
} | ||
|
||
inline fun <reified A : Annotation> DashboardTabProvider( | ||
slug: String, | ||
url_path_prefix: String, | ||
name: String, | ||
category: String = "Admin" | ||
) = DashboardTabProvider( | ||
slug = slug, | ||
url_path_prefix = url_path_prefix, | ||
name = name, | ||
category = category, | ||
accessAnnotation = A::class | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
86 changes: 86 additions & 0 deletions
86
misk/src/test/kotlin/misk/web/actions/AdminDashboardTabActionTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package misk.web.actions | ||
|
||
import com.squareup.moshi.Moshi | ||
import misk.client.HttpClientEndpointConfig | ||
import misk.client.HttpClientFactory | ||
import misk.moshi.adapter | ||
import misk.security.authz.FakeCallerAuthenticator | ||
import misk.testing.MiskTest | ||
import misk.testing.MiskTestModule | ||
import misk.web.jetty.JettyService | ||
import okhttp3.OkHttpClient | ||
import okhttp3.Request | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.jupiter.api.Test | ||
import javax.inject.Inject | ||
import kotlin.test.assertEquals | ||
import kotlin.test.assertNotNull | ||
|
||
@MiskTest(startService = true) | ||
class AdminDashboardTabActionTest { | ||
@MiskTestModule | ||
val module = AdminDashboardActionTestingModule() | ||
|
||
@Inject private lateinit var jetty: JettyService | ||
@Inject private lateinit var httpClientFactory: HttpClientFactory | ||
|
||
val path = "/api/admindashboardtabs" | ||
|
||
@Test fun customCapabilityAccess_unauthenticated() { | ||
val response = executeRequest(path = path) | ||
assertEquals(0, response.adminDashboardTabs.size) | ||
} | ||
|
||
@Test fun customCapabilityAccess_unauthorized() { | ||
val response = executeRequest( | ||
path = path, | ||
user = "sandy", | ||
capabilities = "guest" | ||
) | ||
assertEquals(0, response.adminDashboardTabs.size) | ||
} | ||
|
||
@Test fun customCapabilityAccess_authorized() { | ||
val response = executeRequest( | ||
path = path, | ||
user = "sandy", | ||
capabilities = "admin_access" | ||
) | ||
assertEquals(2, response.adminDashboardTabs.size) | ||
assertNotNull(response.adminDashboardTabs.find { it.name == "Config" }) | ||
assertNotNull(response.adminDashboardTabs.find { it.name == "Web Actions" }) | ||
} | ||
|
||
private fun executeRequest( | ||
path: String = "/", | ||
service: String? = null, | ||
user: String? = null, | ||
capabilities: String? = null | ||
): AdminDashboardTabAction.Response { | ||
val client = createOkHttpClient() | ||
|
||
val baseUrl = jetty.httpServerUrl | ||
val requestBuilder = Request.Builder() | ||
.url(baseUrl.resolve(path)!!) | ||
service?.let { | ||
requestBuilder.header(FakeCallerAuthenticator.SERVICE_HEADER, service) | ||
} | ||
user?.let { | ||
requestBuilder.header(FakeCallerAuthenticator.USER_HEADER, user) | ||
} | ||
capabilities?.let { | ||
requestBuilder.header(FakeCallerAuthenticator.CAPABILITIES_HEADER, capabilities) | ||
} | ||
val call = client.newCall(requestBuilder.build()) | ||
val response = call.execute() | ||
|
||
val moshi = Moshi.Builder().build() | ||
val responseAdaptor = moshi.adapter<AdminDashboardTabAction.Response>() | ||
return responseAdaptor.fromJson(response.body!!.source())!! | ||
} | ||
|
||
private fun createOkHttpClient(): OkHttpClient { | ||
val config = HttpClientEndpointConfig(jetty.httpServerUrl.toString()) | ||
return httpClientFactory.create(config) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.