forked from gearpump/gearpump
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix gearpump#1247, add authentication for UI
- Loading branch information
Showing
38 changed files
with
1,653 additions
and
339 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
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
67 changes: 67 additions & 0 deletions
67
core/src/main/scala/io/gearpump/security/Authenticator.scala
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,67 @@ | ||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one | ||
* or more contributor license agreements. See the NOTICE file | ||
* distributed with this work for additional information | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you under the Apache License, Version 2.0 (the | ||
* "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.gearpump.security | ||
import io.gearpump.security.Authenticator.AuthenticationResult | ||
|
||
import scala.concurrent.{ExecutionContext, Future} | ||
|
||
|
||
/** | ||
* Authenticator for UI dashboard. | ||
* | ||
* Sub Class must implement a constructor with signature like this: | ||
* this(config: Config) | ||
* | ||
*/ | ||
trait Authenticator { | ||
|
||
// TODO: Change the signature to return more attributes of user | ||
// credentials... | ||
def authenticate(user: String, password: String, ec: ExecutionContext): Future[AuthenticationResult] | ||
} | ||
|
||
object Authenticator { | ||
|
||
trait AuthenticationResult { | ||
|
||
def authenticated: Boolean | ||
|
||
def permissionLevel: Int | ||
} | ||
|
||
val UnAuthenticated = new AuthenticationResult{ | ||
override val authenticated = false | ||
override val permissionLevel = -1 | ||
} | ||
|
||
val Guest = new AuthenticationResult{ | ||
override val authenticated = true | ||
override val permissionLevel = 1000 | ||
} | ||
|
||
val User = new AuthenticationResult{ | ||
override val authenticated = true | ||
override val permissionLevel = 1000 + Guest.permissionLevel | ||
} | ||
|
||
val Admin = new AuthenticationResult{ | ||
override val authenticated = true | ||
override val permissionLevel = 1000 + User.permissionLevel | ||
} | ||
} |
110 changes: 110 additions & 0 deletions
110
core/src/main/scala/io/gearpump/security/ConfigFileBasedAuthenticator.scala
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,110 @@ | ||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one | ||
* or more contributor license agreements. See the NOTICE file | ||
* distributed with this work for additional information | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you under the Apache License, Version 2.0 (the | ||
* "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package io.gearpump.security | ||
|
||
import io.gearpump.security.Authenticator.AuthenticationResult | ||
import io.gearpump.security.ConfigFileBasedAuthenticator._ | ||
import com.typesafe.config.Config | ||
import scala.concurrent.{ExecutionContext, Future} | ||
|
||
object ConfigFileBasedAuthenticator { | ||
|
||
private val ROOT = "gearpump.security.config-file-based-authenticator" | ||
private val ADMINS = ROOT + "." + "admins" | ||
private val USERS = ROOT + "." + "users" | ||
private val GUESTS = ROOT + "." + "guests" | ||
|
||
private case class Credentials(admins: Map[String, String], users: Map[String, String], guests: Map[String, String]) { | ||
def verify(user: String, password: String): AuthenticationResult = { | ||
if (admins.contains(user)) { | ||
if (verify(user, password, admins)) { | ||
Authenticator.Admin | ||
} else { | ||
Authenticator.UnAuthenticated | ||
} | ||
} else if (users.contains(user)) { | ||
if (verify(user, password, users)) { | ||
Authenticator.User | ||
} else { | ||
Authenticator.UnAuthenticated | ||
} | ||
} else if (guests.contains(user)) { | ||
if (verify(user, password, guests)) { | ||
Authenticator.Guest | ||
} else { | ||
Authenticator.UnAuthenticated | ||
} | ||
} else { | ||
Authenticator.UnAuthenticated | ||
} | ||
} | ||
|
||
private def verify(user: String, password: String, map: Map[String, String]): Boolean = { | ||
val storedPass = map(user) | ||
PasswordUtil.verify(password, storedPass) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* UI dashboard authenticator based on configuration file. | ||
* | ||
* It has three categories of users: admins, users, and guests. | ||
* admins have unlimited permission, like shutdown a cluster, add/remove machines. | ||
* users have limited permission to submit an application and etc.. | ||
* guests can not submit/kill applications, but can view the application status. | ||
* | ||
* see conf/gear.conf section gearpump.security.config-file-based-authenticator to find information | ||
* about how to configure this authenticator. | ||
* | ||
* [Security consideration] | ||
* It will keep one-way sha1 digest of password instead of password itself. The original password is NOT | ||
* kept in any way, so generally it is safe. | ||
* | ||
* digesting flow (from original password to digest): | ||
* random salt byte array of length 8 -> byte array of (salt + sha1(salt, password)) -> base64Encode | ||
* | ||
* verification user input password with stored digest: | ||
* base64Decode -> extract salt -> do sha1(salt, password) -> generate digest: salt + sha1 -> | ||
* compare the generated digest with the stored digest. | ||
* | ||
*/ | ||
class ConfigFileBasedAuthenticator(config: Config) extends Authenticator { | ||
|
||
private val credentials = loadCredentials(config) | ||
|
||
override def authenticate(user: String, password: String, ec: ExecutionContext): Future[AuthenticationResult] = { | ||
implicit val ctx = ec | ||
Future { | ||
credentials.verify(user, password) | ||
} | ||
} | ||
|
||
private def loadCredentials(config: Config): Credentials = { | ||
val admins = configToMap(config, ADMINS) | ||
val users = configToMap(config, USERS) | ||
val guests = configToMap(config, GUESTS) | ||
new Credentials(admins, users, guests) | ||
} | ||
|
||
private def configToMap(config : Config, path: String) = { | ||
import scala.collection.JavaConverters._ | ||
config.getConfig(path).root.unwrapped.asScala.toMap map { case (k, v) => k -> v.toString } | ||
} | ||
} |
Oops, something went wrong.