-
Notifications
You must be signed in to change notification settings - Fork 95
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4682 from kgudel/authService
Add new service for updating users in kecyloak
- Loading branch information
Showing
19 changed files
with
715 additions
and
69 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
package hmda.auth | ||
|
||
case class VerifiedToken(token: String, id: String, name: String, username: String, email: String, roles: Seq[String], lei: String) | ||
case class VerifiedToken(token: String, id: String, userId: String, name: String, username: String, email: String, roles: Seq[String], lei: String) | ||
|
||
object VerifiedToken { | ||
def apply(): VerifiedToken = | ||
VerifiedToken("empty-token", "dev", "token", "dev", "dev@dev.com", Seq.empty, "lei") | ||
VerifiedToken("empty-token", "11111111-1111-1111-1111-111111111111", "22222222-2222-2222-2222-222222222222", "token", "dev", "dev@dev.com", Seq.empty, "lei") | ||
} |
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,40 @@ | ||
# HMDA Auth API | ||
|
||
## Health Endpoint | ||
|
||
```shell | ||
curl -XGET {{host}}:9095 | ||
``` | ||
|
||
The response should be similar to the following: | ||
|
||
```json | ||
{ | ||
"status":"OK", | ||
"service": | ||
"hmda-auth-api", | ||
"time":"2018-08-08T19:08:20.655Z", | ||
"host":"{{host}}" | ||
} | ||
``` | ||
|
||
## Update User Account | ||
|
||
```shell | ||
curl --location --request PUT 'https://host:9095/users/' \ | ||
--header 'Content-Type: application/json' \ | ||
--header 'Authorization: Bearer {{token}}' \ | ||
--data '{"firstName": "{{user first name}}", "lastName": "{{user last name}}", "leis": ["{{lei1}}", "{{lei2}}"]}' | ||
``` | ||
|
||
Response: | ||
|
||
```json | ||
{ | ||
"firstName": "{{user first name}}", | ||
"lastName": "{{user last name}}", | ||
"leis": [ | ||
"{{lei1}}", "{{lei2}}" | ||
] | ||
} | ||
``` |
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,46 @@ | ||
akka { | ||
log-level = INFO | ||
http.server.preview.enable-http2 = on | ||
} | ||
|
||
akka.http.parsing { | ||
max-to-strict-bytes = 20m | ||
} | ||
akka.http.server.parsing { | ||
max-content-length = 20m | ||
} | ||
|
||
akka.http.server.request-timeout = 4888888 seconds | ||
|
||
hmda { | ||
auth { | ||
http { | ||
timeout = 400000 | ||
host = "0.0.0.0" | ||
host = ${?HTTP_FILE_PROXY_HOST} | ||
port = 9095 | ||
port = ${?HTTP_FILE_PROXY_PORT} | ||
timeout = 10 | ||
} | ||
} | ||
runtime.mode = "dev" | ||
runtime.mode = ${?HMDA_RUNTIME_MODE} | ||
} | ||
|
||
keycloak { | ||
realm = "hmda2" | ||
client.id = "hmda2-api" | ||
client.id = ${?KEYCLOAK_HMDA_API_CLIENT_ID} | ||
public.key = "AYUeqDHLF_GFsZYOSMXzhBT4zyQS--KiEmBFvMzJrBA" | ||
public.key = ${?KEYCLOAK_PUBLIC_KEY_ID} | ||
auth.server.url = "https://ffiec.cfpb.gov/auth/" | ||
auth.server.url = ${?KEYCLOAK_AUTH_URL} | ||
hmda.admin.role = "hmda-admin" | ||
hmda.admin.role = ${?KEYCLOAK_HMDA_ADMIN_ROLE} | ||
admin { | ||
username = "keycloak" | ||
username = ${?KEYCLOAK_ADMIN_USERNAME} | ||
password = "keycloak" | ||
password = ${?KEYCLOAK_ADMIN_PASSWORD} | ||
} | ||
} |
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,95 @@ | ||
package hmda.authService.api | ||
|
||
import akka.http.scaladsl.marshalling.ToResponseMarshallable | ||
import akka.http.scaladsl.model.StatusCodes | ||
import akka.http.scaladsl.model.StatusCodes.BadRequest | ||
import akka.http.scaladsl.server.Directives._ | ||
import akka.http.scaladsl.server.directives.RouteDirectives.complete | ||
import akka.http.scaladsl.server.Route | ||
import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._ | ||
import io.circe.generic.auto._ | ||
import org.slf4j.Logger | ||
|
||
import com.typesafe.config.ConfigFactory | ||
|
||
import slick.basic.DatabaseConfig | ||
import slick.jdbc.JdbcProfile | ||
|
||
import scala.collection.JavaConverters._ | ||
import scala.concurrent._ | ||
import scala.concurrent.Future | ||
import scala.util.{ Failure, Success} | ||
|
||
import hmda.auth.OAuth2Authorization | ||
import hmda.authService.model.UserUpdate | ||
import hmda.api.http.model.ErrorResponse | ||
import hmda.institution.query.InstitutionEmailComponent | ||
|
||
import org.keycloak.admin.client.KeycloakBuilder | ||
import org.keycloak.admin.client.resource._ | ||
import org.keycloak.representations.idm._ | ||
|
||
|
||
object AuthHttpApi { | ||
def create(log: Logger)(implicit ec: ExecutionContext): OAuth2Authorization => Route = new AuthHttpApi(log).authHttpRoutes _ | ||
} | ||
private class AuthHttpApi(log: Logger)(implicit ec: ExecutionContext) extends InstitutionEmailComponent{ | ||
|
||
val config = ConfigFactory.load() | ||
val dbConfig = DatabaseConfig.forConfig[JdbcProfile]("institution_db") | ||
|
||
val keycloakServerUrl = config.getString("keycloak.auth.server.url") | ||
val keycloakRealm = config.getString("keycloak.realm") | ||
val keycloakAdminUsername = config.getString("keycloak.admin.username") | ||
val keycloakAdminPassword = config.getString("keycloak.admin.password") | ||
|
||
implicit val institutionEmailsRepository: InstitutionEmailsRepository = new InstitutionEmailsRepository(dbConfig) | ||
|
||
val keycloak = KeycloakBuilder.builder() | ||
.serverUrl(keycloakServerUrl) | ||
.realm("master") | ||
.clientId("admin-cli") | ||
.grantType("password") | ||
.username(keycloakAdminUsername) | ||
.password(keycloakAdminPassword) | ||
.build() | ||
|
||
def authHttpRoutes(oAuth2Authorization: OAuth2Authorization): Route = { | ||
encodeResponse { | ||
pathPrefix("user") { | ||
(extractUri & put) { uri => | ||
oAuth2Authorization.authorizeVerifiedToken() { token => | ||
entity(as[UserUpdate]){ userUpdate => | ||
val allUsers: UsersResource = keycloak.realm(keycloakRealm).users() | ||
val userResource: UserResource = allUsers.get(token.userId) | ||
val user: UserRepresentation = userResource.toRepresentation() | ||
val fIsValidLeis = verifyLeis(getDomainFromEmail(token.email), userUpdate.leis) | ||
if (userUpdate.firstName != user.getFirstName()) user.setFirstName(userUpdate.firstName) | ||
if (userUpdate.lastName != user.getLastName()) user.setLastName(userUpdate.lastName) | ||
onComplete(fIsValidLeis) { | ||
case Success(isValidLeis) => | ||
if (isValidLeis) { | ||
user.setAttributes(Map(("lei", List(userUpdate.leis.mkString(",")).asJava)).asJava) | ||
userResource.update(user) | ||
complete(ToResponseMarshallable(userUpdate)) | ||
} | ||
else complete((BadRequest, ErrorResponse(BadRequest.intValue, "LEIs are not authorized for the users email domain", uri.path))) | ||
case Failure(e) => | ||
complete((BadRequest, ErrorResponse(StatusCodes.InternalServerError.intValue, "Failed to fetch list of authorized LEIs for users email domain", uri.path))) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
private def getDomainFromEmail(email: String): String = email.split("@")(1) | ||
|
||
private def verifyLeis(emailDomain: String, proposedLeis: List[String]): Future[Boolean] = { | ||
findByEmailAnyYear(emailDomain).map { institutions => | ||
val availableLeis = institutions.map(institution => institution.LEI) | ||
proposedLeis.forall(availableLeis.contains) | ||
} | ||
} | ||
} |
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,39 @@ | ||
package hmda.authService.api | ||
|
||
import akka.actor.typed.Behavior | ||
import akka.actor.typed.scaladsl.Behaviors | ||
import akka.actor.typed.scaladsl.adapter._ | ||
import akka.actor.{ ActorSystem, CoordinatedShutdown } | ||
import akka.http.scaladsl.server.Directives._ | ||
import hmda.api.http.routes.BaseHttpApi | ||
import hmda.api.http.directives.HmdaTimeDirectives._ | ||
import hmda.auth.OAuth2Authorization | ||
import akka.util.Timeout | ||
|
||
import scala.concurrent.ExecutionContext | ||
import scala.concurrent.duration._ | ||
|
||
// This is just a Guardian for starting up the API | ||
// $COVERAGE-OFF$ | ||
object HmdaAuthApi { | ||
val name: String = "hmda-auth-api" | ||
|
||
def apply(): Behavior[Nothing] = Behaviors.setup[Nothing] { ctx => | ||
implicit val system: ActorSystem = ctx.system.toClassic | ||
implicit val ec: ExecutionContext = ctx.executionContext | ||
val shutdown = CoordinatedShutdown(system) | ||
val config = ctx.system.settings.config | ||
implicit val timeout: Timeout = Timeout(config.getInt("hmda.auth.http.timeout").seconds) | ||
val log = ctx.log | ||
val oAuth2Authorization = OAuth2Authorization(log, config) | ||
val authRoute = AuthHttpApi.create(log) | ||
val routes = BaseHttpApi.routes(name) ~ authRoute(oAuth2Authorization) | ||
val host: String = config.getString("hmda.auth.http.host") | ||
val port: Int = config.getInt("hmda.auth.http.port") | ||
|
||
BaseHttpApi.runServer(shutdown, name)(timed(routes), host, port) | ||
Behaviors.empty | ||
} | ||
} | ||
// This is just a Guardian for starting up the API | ||
// $COVERAGE-OFF$ |
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,29 @@ | ||
package hmda.authService | ||
|
||
import akka.actor.typed.ActorSystem | ||
import akka.actor.typed.scaladsl.adapter._ | ||
import akka.actor.{ActorSystem => ClassicActorSystem} | ||
import akka.stream.Materializer | ||
import scala.concurrent.ExecutionContext | ||
import hmda.authService.api.HmdaAuthApi | ||
import org.slf4j.LoggerFactory | ||
|
||
object HmdaAuth extends App { | ||
|
||
val log = LoggerFactory.getLogger("hmda") | ||
|
||
log.info(""" | ||
_ _ _ _ _ _ | ||
| | | |_ __ ___ __| | __ _ / \ _ _| |_| |__ | ||
| |_| | '_ ` _ \ / _` |/ _` | / _ \| | | | __| '_ \ | ||
| _ | | | | | | (_| | (_| | / ___ \ |_| | |_| | | | | ||
|_| |_|_| |_| |_|\__,_|\__,_| /_/ \_\__,_|\__|_| |_| | ||
""".stripMargin) | ||
|
||
implicit val classicSystem: ClassicActorSystem = ClassicActorSystem("hmda-auth-system") | ||
implicit val system: ActorSystem[_] = classicSystem.toTyped | ||
implicit val materializer: Materializer = Materializer(system) | ||
implicit val ec: ExecutionContext = system.executionContext | ||
|
||
ActorSystem[Nothing](HmdaAuthApi(), HmdaAuthApi.name) | ||
} |
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,12 @@ | ||
package hmda.authService.model | ||
|
||
import io.circe.Decoder | ||
import io.circe.Encoder | ||
import io.circe.generic.semiauto._ | ||
|
||
case class UserUpdate(firstName: String, lastName: String, leis: List[String]) | ||
|
||
object UserUpdate { | ||
implicit val decoder: Decoder[UserUpdate] = deriveDecoder[UserUpdate] | ||
implicit val encoder: Encoder[UserUpdate] = deriveEncoder[UserUpdate] | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
apiVersion: v1 | ||
appVersion: "2.7.2" | ||
description: HMDA Auth Service | ||
name: hmda-auth | ||
version: 2.7.2 |
Empty file.
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,32 @@ | ||
{{/* vim: set filetype=mustache: */}} | ||
{{/* | ||
Expand the name of the chart. | ||
*/}} | ||
{{- define "hmda-auth.name" -}} | ||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} | ||
{{- end -}} | ||
|
||
{{/* | ||
Create a default fully qualified app name. | ||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). | ||
If release name contains chart name it will be used as a full name. | ||
*/}} | ||
{{- define "hmda-auth.fullname" -}} | ||
{{- if .Values.fullnameOverride -}} | ||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} | ||
{{- else -}} | ||
{{- $name := default .Chart.Name .Values.nameOverride -}} | ||
{{- if contains $name .Release.Name -}} | ||
{{- .Release.Name | trunc 63 | trimSuffix "-" -}} | ||
{{- else -}} | ||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} | ||
{{- end -}} | ||
{{- end -}} | ||
{{- end -}} | ||
|
||
{{/* | ||
Create chart name and version as used by the chart label. | ||
*/}} | ||
{{- define "hmda-auth.chart" -}} | ||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} | ||
{{- end -}} |
Oops, something went wrong.