improve security
This commit is contained in:
parent
62dc62fbb6
commit
67579efa8a
|
@ -41,7 +41,7 @@ object Auth extends LilaController {
|
|||
implicit val req = ctx.body
|
||||
forms.signup.bindFromRequest.fold(
|
||||
err ⇒ BadRequest(html.auth.signup(err, forms.captchaCreate)),
|
||||
data ⇒ {
|
||||
data ⇒ Firewall {
|
||||
val user = userRepo.create(data.username, data.password).unsafePerformIO
|
||||
gotoSignupSucceeded(
|
||||
user.err("register error").username
|
||||
|
|
|
@ -21,15 +21,15 @@ object ForumPost extends LilaController with forum.Controller {
|
|||
case (categ, topic, posts) ⇒ forms.post.bindFromRequest.fold(
|
||||
err ⇒ BadRequest(html.forum.topic.show(
|
||||
categ, topic, posts, Some(err -> forms.captchaCreate))),
|
||||
data ⇒ (for {
|
||||
post ← postApi.makePost(categ, topic, data, ctx.me)
|
||||
} yield Redirect("%s#%d".format(
|
||||
data ⇒ Firewall {
|
||||
val post = postApi.makePost(categ, topic, data, ctx.me).unsafePerformIO
|
||||
Redirect("%s#%d".format(
|
||||
routes.ForumTopic.show(
|
||||
categ.slug,
|
||||
topic.slug,
|
||||
categ.slug,
|
||||
topic.slug,
|
||||
postApi lastPageOf topic.incNbPosts),
|
||||
post.number)
|
||||
)).unsafePerformIO
|
||||
post.number))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,10 +26,10 @@ object ForumTopic extends LilaController with forum.Controller {
|
|||
IOptionResult(categRepo bySlug categSlug) { categ ⇒
|
||||
forms.topic.bindFromRequest.fold(
|
||||
err ⇒ BadRequest(html.forum.topic.form(categ, err, forms.captchaCreate)),
|
||||
data ⇒ (for {
|
||||
topic ← topicApi.makeTopic(categ, data, ctx.me)
|
||||
} yield Redirect(routes.ForumTopic.show(categ.slug, topic.slug, 1))
|
||||
).unsafePerformIO
|
||||
data ⇒ Firewall {
|
||||
val topic = topicApi.makeTopic(categ, data, ctx.me).unsafePerformIO
|
||||
Redirect(routes.ForumTopic.show(categ.slug, topic.slug, 1))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,6 +67,12 @@ trait LilaController
|
|||
ctx.isGranted(perm).fold(f(ctx)(me), authorizationFailed(ctx.req))
|
||||
}
|
||||
|
||||
def Firewall[A <: Result](a: ⇒ A)(implicit ctx: Context): Result =
|
||||
env.security.firewall.accepts(ctx.req).fold(
|
||||
a,
|
||||
Redirect(routes.Lobby.home())
|
||||
)
|
||||
|
||||
def JsonOk(map: Map[String, Any]) = Ok(toJson(map)) as JSON
|
||||
|
||||
def JsonOk(list: List[String]) = Ok(Json generate list) as JSON
|
||||
|
|
|
@ -17,14 +17,4 @@ object Main extends LilaController {
|
|||
uidOption = get("uid"),
|
||||
username = ctx.me map (_.username))
|
||||
}
|
||||
|
||||
val blocked = Action { implicit req ⇒
|
||||
Async {
|
||||
Akka.future {
|
||||
println("BLOCK %s %s".format(req.remoteAddress, req))
|
||||
Thread sleep 10 * 1000
|
||||
BadRequest(html.base.blocked())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,7 +111,7 @@ object User extends LilaController {
|
|||
IOptionIORedirect(userRepo byId username) { user ⇒
|
||||
for {
|
||||
spy ← securityStore userSpy username
|
||||
_ ← io(spy.ips foreach firewall.block)
|
||||
_ ← io(spy.ips foreach firewall.blockIp)
|
||||
_ ← user.isChatBan.fold(io(), lobbyMessenger mute username)
|
||||
} yield routes.User show username
|
||||
}
|
||||
|
|
|
@ -82,6 +82,7 @@ final class Settings(config: Config) {
|
|||
|
||||
val FirewallCacheTtl = millis("firewall.cache_ttl")
|
||||
val FirewallEnabled = getBoolean("firewall.enabled")
|
||||
val FirewallBlockCookie = getString("firewall.block_cookie")
|
||||
|
||||
val ActorReporting = "reporting"
|
||||
val ActorSiteHub = "site_hub"
|
||||
|
|
|
@ -10,16 +10,16 @@ object LilaCookie {
|
|||
private def domain(req: RequestHeader): String =
|
||||
domainRegex.replaceAllIn(req.domain, _ group 1)
|
||||
|
||||
def apply(name: String, value: String)(implicit req: RequestHeader): Cookie = {
|
||||
val data = req.session + (name -> value)
|
||||
val encoded = Session.encode(Session.serialize(data))
|
||||
Cookie(
|
||||
Session.COOKIE_NAME,
|
||||
encoded,
|
||||
Session.maxAge,
|
||||
"/",
|
||||
domain(req).some,
|
||||
Session.secure,
|
||||
Session.httpOnly)
|
||||
}
|
||||
def session(name: String, value: String)(implicit req: RequestHeader): Cookie = cookie(
|
||||
Session.COOKIE_NAME,
|
||||
Session.encode(Session.serialize(req.session + (name -> value))))
|
||||
|
||||
def cookie(name: String, value: String)(implicit req: RequestHeader): Cookie = Cookie(
|
||||
name,
|
||||
value,
|
||||
Session.maxAge,
|
||||
"/",
|
||||
domain(req).some,
|
||||
Session.secure,
|
||||
Session.httpOnly)
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ final class Setting(ctx: Context) extends HttpEnvironment {
|
|||
ctx.me.fold(
|
||||
m ⇒ userRepo.saveSetting(m, name, value.toString),
|
||||
io()) map { _ ⇒
|
||||
LilaCookie(name, value.toString)(ctx.req)
|
||||
LilaCookie.session(name, value.toString)(ctx.req)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ trait AuthImpl {
|
|||
Redirect(routes.Lobby.home)
|
||||
|
||||
def authenticationFailed(implicit req: RequestHeader): PlainResult =
|
||||
Redirect(routes.Lobby.home) withCookies LilaCookie("access_uri", req.uri)
|
||||
Redirect(routes.Lobby.home) withCookies LilaCookie.session("access_uri", req.uri)
|
||||
|
||||
def saveAuthentication(username: String)(implicit req: RequestHeader): String =
|
||||
(OrnicarRandom nextAsciiString 12) ~ { sessionId ⇒
|
||||
|
@ -41,12 +41,12 @@ trait AuthImpl {
|
|||
|
||||
def gotoLoginSucceeded[A](username: String)(implicit req: RequestHeader) = {
|
||||
val sessionId = saveAuthentication(username)
|
||||
loginSucceeded(req) withCookies LilaCookie("sessionId", sessionId)
|
||||
loginSucceeded(req) withCookies LilaCookie.session("sessionId", sessionId)
|
||||
}
|
||||
|
||||
def gotoSignupSucceeded[A](username: String)(implicit req: RequestHeader) = {
|
||||
val sessionId = saveAuthentication(username)
|
||||
Redirect(routes.User.show(username)) withCookies LilaCookie("sessionId", sessionId)
|
||||
Redirect(routes.User.show(username)) withCookies LilaCookie.session("sessionId", sessionId)
|
||||
}
|
||||
|
||||
def gotoLogoutSucceeded(implicit req: RequestHeader) = {
|
||||
|
@ -68,6 +68,7 @@ trait AuthImpl {
|
|||
|
||||
def restoreUser(req: RequestHeader): Option[User] = for {
|
||||
sessionId ← req.session.get("sessionId")
|
||||
if env.security.firewall accepts req
|
||||
username ← env.security.store.getUsername(sessionId)
|
||||
user ← (env.user.userRepo byId username).unsafePerformIO
|
||||
} yield user
|
||||
|
|
|
@ -2,36 +2,62 @@ package lila
|
|||
package security
|
||||
|
||||
import memo.MonoMemo
|
||||
import http.LilaCookie
|
||||
import controllers.routes
|
||||
|
||||
import play.api.mvc.{ Action, RequestHeader, Handler }
|
||||
import play.api.mvc.Results.BadRequest
|
||||
import play.api.mvc.{ RequestHeader, Handler, Action, Cookies }
|
||||
import play.api.mvc.Results.Redirect
|
||||
import com.mongodb.casbah.MongoCollection
|
||||
import com.mongodb.casbah.Imports._
|
||||
import scalaz.effects._
|
||||
import java.util.Date
|
||||
import ornicar.scalalib.OrnicarRandom
|
||||
|
||||
final class Firewall(
|
||||
collection: MongoCollection,
|
||||
cacheTtl: Int,
|
||||
blockCookieName: String,
|
||||
enabled: Boolean) {
|
||||
|
||||
def requestHandler(req: RequestHeader): Option[Handler] =
|
||||
(enabled && blocks(req.remoteAddress)) option controllers.Main.blocked
|
||||
def requestHandler(implicit req: RequestHeader): Option[Handler] = if (enabled) {
|
||||
val bIp = blocksIp(req.remoteAddress)
|
||||
val bCs = blocksCookies(req.cookies)
|
||||
if (bIp && !bCs) infectCookie.some
|
||||
else if (bCs && !bIp) { blockIp(req.remoteAddress); none }
|
||||
else none
|
||||
}
|
||||
else none
|
||||
|
||||
def blocks(ip: String) = ips.apply contains ip
|
||||
def blocks(req: RequestHeader): Boolean =
|
||||
enabled && (blocksIp(req.remoteAddress) || blocksCookies(req.cookies))
|
||||
|
||||
def accepts(ip: String) = !blocks(ip)
|
||||
def accepts(req: RequestHeader): Boolean = !blocks(req)
|
||||
|
||||
def block(ip: String) {
|
||||
def blockIp(ip: String) {
|
||||
if (validIp(ip)) {
|
||||
if (accepts(ip)) {
|
||||
if (!blocksIp(ip)) {
|
||||
log("Block IP: " + ip)
|
||||
collection += DBObject("_id" -> ip, "date" -> new Date)
|
||||
ips.refresh
|
||||
}
|
||||
}
|
||||
else println("Invalid IP block: " + ip)
|
||||
else log("Invalid IP block: " + ip)
|
||||
}
|
||||
|
||||
def infectCookie(implicit req: RequestHeader) = Action {
|
||||
log("Infect cookie for IP: " + req.remoteAddress)
|
||||
val cookie = LilaCookie.cookie(blockCookieName, OrnicarRandom nextAsciiString 32)
|
||||
Redirect(routes.Lobby.home()) withCookies cookie
|
||||
}
|
||||
|
||||
private def log(msg: Any) {
|
||||
println("[%s] %s".format("firewall", msg.toString))
|
||||
}
|
||||
|
||||
private def blocksIp(ip: String) = ips.apply contains ip
|
||||
|
||||
private def blocksCookies(cookies: Cookies) = (cookies get blockCookieName).isDefined
|
||||
|
||||
// http://stackoverflow.com/questions/106179/regular-expression-to-match-hostname-or-ip-address
|
||||
private val ipRegex = """^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$""".r
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ final class SecurityEnv(
|
|||
lazy val firewall = new security.Firewall(
|
||||
collection = mongodb(MongoCollectionFirewall),
|
||||
cacheTtl = FirewallCacheTtl,
|
||||
blockCookieName = FirewallBlockCookie,
|
||||
enabled = FirewallEnabled)
|
||||
|
||||
lazy val flood = new security.Flood
|
||||
|
|
|
@ -21,8 +21,8 @@ case class User(
|
|||
|
||||
def canChat =
|
||||
!isChatBan &&
|
||||
nbGames >= 3 /* &&
|
||||
createdAt < (DateTime.now - 1.day) */
|
||||
nbGames >= 3 &&
|
||||
createdAt < (DateTime.now - 3.hours)
|
||||
|
||||
def disabled = !enabled
|
||||
|
||||
|
|
|
@ -96,6 +96,7 @@ session {
|
|||
firewall {
|
||||
cache_ttl=5 seconds
|
||||
enabled=true
|
||||
block_cookie=fEKHA4zI23ZrZrom
|
||||
}
|
||||
|
||||
# trust proxy X-Forwarded-For header
|
||||
|
|
Loading…
Reference in a new issue