forked from sscarduzio/elasticsearch-readonlyrest-plugin
/
ExternalAuthenticationService.scala
108 lines (90 loc) · 4.32 KB
/
ExternalAuthenticationService.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
/*
* This file is part of ReadonlyREST.
*
* ReadonlyREST is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ReadonlyREST is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ReadonlyREST. If not, see http://www.gnu.org/licenses/
*/
package tech.beshu.ror.acl.blocks.definitions
import java.nio.charset.Charset
import cats.{Eq, Show}
import cats.implicits._
import com.softwaremill.sttp._
import cz.seznam.euphoria.shaded.guava.com.google.common.hash.Hashing
import eu.timepit.refined.api.Refined
import eu.timepit.refined.numeric.Positive
import monix.eval.Task
import tech.beshu.ror.acl.blocks.definitions.CacheableExternalAuthenticationServiceDecorator.HashedUserCredentials
import tech.beshu.ror.acl.domain.{BasicAuth, Header, Secret, User}
import tech.beshu.ror.acl.blocks.definitions.ExternalAuthenticationService.Name
import tech.beshu.ror.acl.domain
import tech.beshu.ror.acl.factory.HttpClientsFactory.HttpClient
import tech.beshu.ror.acl.factory.decoders.definitions.Definitions.Item
import tech.beshu.ror.acl.utils.CacheableActionWithKeyMapping
import scala.concurrent.duration.FiniteDuration
trait ExternalAuthenticationService extends Item {
override type Id = Name
def id: Id
def authenticate(user: User.Id, secret: Secret): Task[Boolean]
override implicit def show: Show[Name] = Name.nameShow
}
object ExternalAuthenticationService {
final case class Name(value: String) extends AnyVal
object Name {
implicit val nameEq: Eq[Name] = Eq.fromUniversalEquals
implicit val nameShow: Show[Name] = Show.show(_.value)
}
}
class BasicAuthHttpExternalAuthenticationService(override val id: ExternalAuthenticationService#Id,
uri: Uri,
successStatusCode: Int,
httpClient: HttpClient)
extends ExternalAuthenticationService {
override def authenticate(user: User.Id, credentials: Secret): Task[Boolean] = {
val basicAuthHeader = BasicAuth(user, credentials).header
httpClient
.send(sttp.get(uri).header(basicAuthHeader.name.value.value, basicAuthHeader.value.value))
.map(_.code === successStatusCode)
}
}
class JwtExternalAuthenticationService(override val id: ExternalAuthenticationService#Id,
uri: Uri,
successStatusCode: Int,
httpClient: HttpClient)
extends ExternalAuthenticationService {
override def authenticate(user: User.Id, secret: Secret): Task[Boolean] = {
httpClient
.send(sttp.get(uri).header(Header.Name.authorization.value.value, s"Bearer ${secret.value}"))
.map(_.code === successStatusCode)
}
}
class CacheableExternalAuthenticationServiceDecorator(underlying: ExternalAuthenticationService,
ttl: FiniteDuration Refined Positive)
extends ExternalAuthenticationService {
private val cacheableAuthentication =
new CacheableActionWithKeyMapping[(User.Id, domain.Secret), HashedUserCredentials, Boolean](ttl, authenticateAction, hashCredential)
override val id: ExternalAuthenticationService#Id = underlying.id
override def authenticate(user: User.Id, secret: Secret): Task[Boolean] = {
cacheableAuthentication.call((user, secret))
}
private def hashCredential(value: (User.Id, domain.Secret)) = {
val (user, secret) = value
HashedUserCredentials(user, Hashing.sha256.hashString(secret.value, Charset.defaultCharset).toString)
}
private def authenticateAction(value: (User.Id, domain.Secret)) = {
val (userId, secret) = value
underlying.authenticate(userId, secret)
}
}
object CacheableExternalAuthenticationServiceDecorator {
private[CacheableExternalAuthenticationServiceDecorator] final case class HashedUserCredentials(user: User.Id, hashedCredentials: String)
}