Skip to content

Commit

Permalink
Add basic authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
fracassi-marco committed Dec 30, 2019
1 parent bcae0fd commit 8ab2b8c
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 3 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ repositories {
```
- Add the dependency
```
implementation 'com.github.DaikonWeb:daikon:0.6.3'
implementation 'com.github.DaikonWeb:daikon:0.6.4'
```

### Maven
Expand All @@ -40,7 +40,7 @@ implementation 'com.github.DaikonWeb:daikon:0.6.3'
<dependency>
<groupId>com.github.DaikonWeb</groupId>
<artifactId>daikon</artifactId>
<version>0.6.3</version>
<version>0.6.4</version>
</dependency>
```

Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {
}

group = 'com.github.DaikonWeb'
version '0.6.3'
version '0.6.4'

repositories {
mavenCentral()
Expand Down
53 changes: 53 additions & 0 deletions src/main/kotlin/daikon/BasicAuthentication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package daikon

import daikon.RequestFlow.halt
import org.eclipse.jetty.http.HttpHeader.AUTHORIZATION
import org.eclipse.jetty.http.HttpHeader.WWW_AUTHENTICATE
import java.nio.charset.StandardCharsets.UTF_8
import java.util.*
import javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED

class BasicAuthentication {

private val credentials = mutableListOf<Credential>()

fun addUser(username: String, password: String) {
credentials.add(Credential(username, password))
}

fun validate(req: Request, res: Response, realm: String) {
try {
val credential = credential(req.header(AUTHORIZATION.asString()))

if (isForbidden(credential)) {
unauthorized(res, realm)
}
} catch (t: Throwable) {
unauthorized(res, realm)
}

}

private fun unauthorized(res: Response, realm: String) {
res.header(
WWW_AUTHENTICATE.asString(),
"""Basic realm="$realm", charset="UTF-8""""
)
halt(res, SC_UNAUTHORIZED)
}

private fun isForbidden(credential: Credential): Boolean {
return credentials.none { it == credential }
}

private fun credential(header: String): Credential {
val credentials = String(
Base64.getDecoder().decode(header.replace("Basic ", "", true)),
UTF_8
).split(":")

return Credential(credentials[0], credentials[1])
}
}

data class Credential(val username: String, val password: String)
12 changes: 12 additions & 0 deletions src/main/kotlin/daikon/HttpServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.eclipse.jetty.util.resource.Resource
import java.time.LocalDateTime.now
import java.time.temporal.ChronoUnit.MILLIS


class HttpServer(private val port: Int = 4545, initializeActions: HttpServer.() -> Unit = {}) : AutoCloseable {

private val routes = Routing()
Expand All @@ -19,6 +20,7 @@ class HttpServer(private val port: Int = 4545, initializeActions: HttpServer.()
private val beforeStopActions = mutableListOf<(Context) -> Unit>()
private val basePath = mutableListOf("")
private val context= ServerContext(port)
private val basicAuth = BasicAuthentication()

init {
initializeActions()
Expand Down Expand Up @@ -149,6 +151,16 @@ class HttpServer(private val port: Int = 4545, initializeActions: HttpServer.()
return this
}
fun basicAuthUser(username: String, password: String): HttpServer {
basicAuth.addUser(username, password)
return this
}
fun basicAuth(path: String, realm: String = "default"): HttpServer {
before(path) { req, res -> basicAuth.validate(req, res, realm) }
return this
}
private fun add(method: Method, path: String, action: RouteAction) {
routes.add(Route(method, joinPaths(path), action))
}
Expand Down
61 changes: 61 additions & 0 deletions src/test/kotlin/daikon/BasicAuthenticationHttpTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package daikon

import daikon.Localhost.get
import khttp.structures.authorization.BasicAuthorization
import org.assertj.core.api.Assertions.assertThat
import org.eclipse.jetty.http.HttpStatus.OK_200
import org.eclipse.jetty.http.HttpStatus.UNAUTHORIZED_401
import org.junit.jupiter.api.Test

class BasicAuthenticationHttpTest {

@Test
fun routing() {
HttpServer()
.get("/") { _, res -> res.status(OK_200)}
.basicAuth("/foo", "realm")
.basicAuth("/bar*")
.basicAuth("/baz/:name")
.start().use {
assertThat(get("/").statusCode).isEqualTo(OK_200)
assertThat(get("/foo").statusCode).isEqualTo(UNAUTHORIZED_401)
assertThat(get("/bar/baz").statusCode).isEqualTo(UNAUTHORIZED_401)
assertThat(get("/baz/alex").statusCode).isEqualTo(UNAUTHORIZED_401)
}
}

@Test
fun `authenticate user`() {
HttpServer()
.basicAuthUser("Marco", "secret")
.basicAuth("/")
.get("/") { _, res -> res.status(OK_200)}
.start().use {
assertThat(get("/").statusCode).isEqualTo(UNAUTHORIZED_401)
assertThat(get("/", auth = BasicAuthorization("Marco", "secret")).statusCode).isEqualTo(OK_200)
}
}

@Test
fun `wrong credential`() {
HttpServer()
.basicAuthUser("Marco", "secret")
.basicAuth("/")
.get("/") { _, res -> res.status(OK_200)}
.start().use {
assertThat(get("/", auth = BasicAuthorization("Marco", "wrong")).statusCode).isEqualTo(UNAUTHORIZED_401)
assertThat(get("/", auth = BasicAuthorization("wrong", "secret")).statusCode).isEqualTo(UNAUTHORIZED_401)
}
}

@Test
fun `supports utf-8`() {
HttpServer()
.basicAuthUser("ìù", "èéàò")
.basicAuth("/")
.get("/") { _, res -> res.status(OK_200)}
.start().use {
assertThat(get("/", auth = BasicAuthorization("ìù", "èéàò")).statusCode).isEqualTo(OK_200)
}
}
}

0 comments on commit 8ab2b8c

Please sign in to comment.