Skip to content
Permalink
Browse files

Remove duplicate authentication logic in WebTab

  • Loading branch information...
adrw committed Aug 12, 2019
1 parent 9621367 commit 561a8e8887bf031e224d04ca77577e37cc5fdf66
@@ -27,4 +27,16 @@ data class MiskCaller(
"service=$service"
}
}

/** Determine based on allowed capabilities/services if the caller is permitted */
fun isAllowed(allowedCapabilities: Set<String>, allowedServices: Set<String>): Boolean {
// Allow if we don't have any requirements on service or capability
if (allowedServices.isEmpty() && allowedCapabilities.isEmpty()) return true

// Allow if the caller has provided an allowed service
if (service != null && allowedServices.contains(service)) return true

// Allow if the caller has provided an allowed capability
return capabilities.any { allowedCapabilities.contains(it) }
}
}
@@ -19,25 +19,14 @@ class AccessInterceptor private constructor(

override fun intercept(chain: Chain): Any {
val caller = caller.get() ?: throw UnauthenticatedException()
if (!isAllowed(caller)) {
if (!caller.isAllowed(allowedCapabilities, allowedServices)) {
logger.info { "$caller is not allowed to access ${chain.action}" }
throw UnauthorizedException()
}

return chain.proceed(chain.args)
}

private fun isAllowed(caller: MiskCaller): Boolean {
// Allow if we don't have any requirements on service or capability
if (allowedServices.isEmpty() && allowedCapabilities.isEmpty()) return true

// Allow if the caller has provided an allowed service
if (caller.service != null && allowedServices.contains(caller.service)) return true

// Allow if the caller has provided an allowed capability
return caller.capabilities.any { allowedCapabilities.contains(it) }
}

internal class Factory @Inject internal constructor(
private val caller: @JvmSuppressWildcards ActionScoped<MiskCaller?>,
private val registeredEntries: List<AccessAnnotationEntry>
@@ -64,21 +53,26 @@ class AccessInterceptor private constructor(
// This action is explicitly marked as unauthenticated.
actionEntries.size == 1 && action.hasAnnotation<Unauthenticated>() -> return null
// Successfully return @Authenticated or custom Access Annotation
actionEntries.size == 1 -> return AccessInterceptor(actionEntries[0].services.toSet(), actionEntries[0].capabilities.toSet(), caller)
actionEntries.size == 1 -> return AccessInterceptor(actionEntries[0].services.toSet(),
actionEntries[0].capabilities.toSet(), caller)
// Not exactly one access annotation. Fail with a useful message.
else -> {
val requiredAnnotations = mutableListOf<KClass<out Annotation>>()
requiredAnnotations += Authenticated::class
requiredAnnotations += Unauthenticated::class
requiredAnnotations += registeredEntries.map { it.annotation }
throw IllegalStateException("""You need to register an AccessAnnotationEntry to tell the authorization system which capabilities and services are allowed to access ${action.name}::${action.function.name}(). You can either:
throw IllegalStateException(
"""You need to register an AccessAnnotationEntry to tell the authorization system which capabilities and services are allowed to access ${action.name}::${action.function.name}(). You can either:
|
|A) Add an AccessAnnotationEntry multibinding in a module for one of the annotations on ${action.name}::${action.function.name}():
| ${action.function.annotations}
|
| AccessAnnotationEntry Example Multibinding:
| multibind<AccessAnnotationEntry>().toInstance(
| AccessAnnotationEntry<${action.function.annotations.filter { it.annotationClass.simpleName.toString().endsWith("Access") }.firstOrNull()?.annotationClass?.simpleName ?: "{Access Annotation Class Simple Name}"}>(capabilities = ???, services = ???))
| AccessAnnotationEntry<${action.function.annotations.filter {
it.annotationClass.simpleName.toString().endsWith("Access")
}.firstOrNull()?.annotationClass?.simpleName
?: "{Access Annotation Class Simple Name}"}>(capabilities = ???, services = ???))
|
|B) Add an AccessAnnotation to ${action.name}::${action.function.name}() that already has a matching AccessAnnotationEntry such as:
| $requiredAnnotations
@@ -90,13 +84,13 @@ class AccessInterceptor private constructor(
}

private fun Authenticated.toAccessAnnotationEntry() = AccessAnnotationEntry(
Authenticated::class, services.toList(), capabilities.toList())
Authenticated::class, services.toList(), capabilities.toList())

private fun Unauthenticated.toAccessAnnotationEntry() = AccessAnnotationEntry(
Unauthenticated::class, listOf(), listOf())
Unauthenticated::class, listOf(), listOf())

private inline fun <reified T : Annotation> Action.hasAnnotation() =
function.annotations.any { it.annotationClass == T::class }
function.annotations.any { it.annotationClass == T::class }
}

companion object {
@@ -1,28 +1,10 @@
package misk.web

import misk.MiskCaller

abstract class WebTab(
slug: String,
url_path_prefix: String,
// capabilities, services permissions control visibility of tab to misk web application user
// it does not deal with any other permissions such as static resource access or otherwise
val capabilities: Set<String> = setOf(),
val services: Set<String> = setOf()
) : ValidWebEntry(slug = slug, url_path_prefix = url_path_prefix) {
fun isAuthenticated(caller: MiskCaller?): Boolean = when {
// no capabilities/service requirement => unauthenticated and null caller requests allowed
capabilities.isEmpty() && services.isEmpty() -> true

// capability/service requirement present but caller null => assume authentication broken
caller == null -> false

// matching capability
capabilities.any { caller.capabilities.contains(it) } -> true

// matching service
services.any { caller.service == it } -> true

else -> false
}
}
) : ValidWebEntry(slug = slug, url_path_prefix = url_path_prefix)
@@ -32,12 +32,13 @@ class AdminDashboardTabAction @Inject constructor() : WebAction {
@ResponseContentType(MediaTypes.APPLICATION_JSON)
@Unauthenticated
fun getAll(): Response {
val caller = callerProvider.get()
val authorizedAdminDashboardTabs = adminDashboardTabs.filter { it.isAuthenticated(caller) }
val caller = callerProvider.get() ?: return Response()
val authorizedAdminDashboardTabs =
adminDashboardTabs.filter { caller.isAllowed(it.capabilities, it.services) }
return Response(adminDashboardTabs = authorizedAdminDashboardTabs)
}

data class Response(val adminDashboardTabs: List<DashboardTab>)
data class Response(val adminDashboardTabs: List<DashboardTab> = listOf())
}

@Qualifier

0 comments on commit 561a8e8

Please sign in to comment.
You can’t perform that action at this time.