diff --git a/README.md b/README.md index b1755ff2d..21e4c2690 100644 --- a/README.md +++ b/README.md @@ -468,7 +468,7 @@ There are 3 API's endpoint related to webhooks: ## OpenID Connect In order to enable an OIDC workflow at an instance of OBP-API portal app(login functionality) you need to set-up the following props: ```props -# Google as an identity provider +## Google as an identity provider # openid_connect_1.client_secret=OYdWujJl******_NXzPlDI4T # openid_connect_1.client_id=883**3244***-s4hi72j0rble0iiivq1gn09k7***tdci.apps.googleusercontent.com # openid_connect_1.callback_url=http://127.0.0.1:8080/auth/openid-connect/callback @@ -477,6 +477,18 @@ In order to enable an OIDC workflow at an instance of OBP-API portal app(login f # openid_connect_1.endpoint.token=https://oauth2.googleapis.com/token # openid_connect_1.endpoint.jwks_uri=https://www.googleapis.com/oauth2/v3/certs # openid_connect_1.access_type_offline=false +# openid_connect_1.button_text = Yahoo + +## Yahoo as an identity provider +# openid_connect_2.client_secret=685d47412efd8b74891ad711876558189793e957 +# openid_connect_2.client_id=zg0yJmk9WUEzaERzd1RtMU02JmQ9WVdrOU9FOHpTbXN5TkhNbWNHbzlNQS0tJnM9Y38uc3VtZXJzZWNyZXQmc3Y9MCZ4PWjW +# openid_connect_2.callback_url=https://1aaac045.ngrok.io/auth/openid-connect/callback-2 +# openid_connect_2.endpoint.authorization=https://api.login.yahoo.com/oauth2/request_auth +# openid_connect_2.endpoint.userinfo=https://api.login.yahoo.com/openid/v1/userinfo +# openid_connect_2.endpoint.token=https://api.login.yahoo.com/oauth2/get_token +# openid_connect_2.endpoint.jwks_uri=https://api.login.yahoo.com/openid/v1/certs +# openid_connect_2.access_type_offline=true +# openid_connect_2.button_text = Yahoo ``` Please note in the example above you MUST run OBP-API portal at the URL: http://127.0.0.1:8080 diff --git a/obp-api/src/main/scala/code/api/util/JwtUtil.scala b/obp-api/src/main/scala/code/api/util/JwtUtil.scala index a1f27d3c8..0f1afc1e1 100644 --- a/obp-api/src/main/scala/code/api/util/JwtUtil.scala +++ b/obp-api/src/main/scala/code/api/util/JwtUtil.scala @@ -126,6 +126,25 @@ object JwtUtil extends MdcLoggable { } } + /** + * The Issuer Identifier for the Issuer of the response. + * Get the value of the "iss" claim, or None if it's not available. + * + * @return the Issuer's value or None. + */ + def getAlgorithm(jwtToken: String): Option[JWSAlgorithm] = { + try { + val signedJWT = SignedJWT.parse(jwtToken) + // claims extraction... + Some(signedJWT.getHeader().getAlgorithm()) + } catch { + case e: Exception => + logger.error(msg = "code.api.util.JwtUtil.getAlgorithm") + logger.error(e) + None + } + } + /** * This function validates Access Token * @param accessToken The access token to validate, typically submitted with a HTTP header like @@ -186,7 +205,7 @@ object JwtUtil extends MdcLoggable { val iss: Issuer = new Issuer(getIssuer(idToken).getOrElse("")) val aud = getAudience(idToken).headOption.getOrElse("") val clientID: ClientID = new ClientID(aud) - val jwsAlg: JWSAlgorithm = JWSAlgorithm.RS256 + val jwsAlg: JWSAlgorithm = getAlgorithm(idToken).getOrElse(JWSAlgorithm.RS256) val jwkSetURL: URL = new URL(remoteJWKSetUrl) // Create validator for signed ID tokens diff --git a/obp-api/src/main/scala/code/api/util/migration/Migration.scala b/obp-api/src/main/scala/code/api/util/migration/Migration.scala index 022287b20..3afd52556 100644 --- a/obp-api/src/main/scala/code/api/util/migration/Migration.scala +++ b/obp-api/src/main/scala/code/api/util/migration/Migration.scala @@ -64,6 +64,7 @@ object Migration extends MdcLoggable { bankAccountHoldersAndOwnerViewAccessInfo() alterTableMappedConsent() alterColumnChallengeAtTableMappedConsent() + alterTableOpenIDConnectToken() } private def dummyScript(): Boolean = { @@ -158,6 +159,13 @@ object Migration extends MdcLoggable { MigrationOfMappedConsent.alterColumnChallenge(name) } } + private def alterTableOpenIDConnectToken(): Boolean = { + val name = nameOf(alterTableOpenIDConnectToken) + runOnce(name) { + MigrationOfOpnIDConnectToken.alterColumnAccessToken(name) + MigrationOfOpnIDConnectToken.alterColumnRefreshToken(name) + } + } } diff --git a/obp-api/src/main/scala/code/api/util/migration/MigrationOfOpnIDConnectToken.scala b/obp-api/src/main/scala/code/api/util/migration/MigrationOfOpnIDConnectToken.scala new file mode 100644 index 000000000..fdd153105 --- /dev/null +++ b/obp-api/src/main/scala/code/api/util/migration/MigrationOfOpnIDConnectToken.scala @@ -0,0 +1,83 @@ +package code.api.util.migration + +import java.time.format.DateTimeFormatter +import java.time.{ZoneId, ZonedDateTime} + +import code.api.util.APIUtil +import code.api.util.migration.Migration.{DbFunction, saveLog} +import code.token.OpenIDConnectToken +import net.liftweb.mapper.{DB, Schemifier} +import net.liftweb.util.DefaultConnectionIdentifier + +object MigrationOfOpnIDConnectToken { + + val oneDayAgo = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(1) + val oneYearInFuture = ZonedDateTime.now(ZoneId.of("UTC")).plusYears(1) + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'") + + def alterColumnAccessToken(name: String): Boolean = { + DbFunction.tableExists(OpenIDConnectToken, (DB.use(DefaultConnectionIdentifier){ conn => conn})) match { + case true => + val startDate = System.currentTimeMillis() + val commitId: String = APIUtil.gitCommit + var isSuccessful = false + + val executedSql = + DbFunction.maybeWrite(true, Schemifier.infoF _, DB.use(DefaultConnectionIdentifier){ conn => conn}) { + () => "ALTER TABLE openidconnecttoken ALTER COLUMN accesstoken type text;" + } + + val endDate = System.currentTimeMillis() + val comment: String = + s"""Executed SQL: + |$executedSql + |""".stripMargin + isSuccessful = true + saveLog(name, commitId, isSuccessful, startDate, endDate, comment) + isSuccessful + + case false => + val startDate = System.currentTimeMillis() + val commitId: String = APIUtil.gitCommit + val isSuccessful = false + val endDate = System.currentTimeMillis() + val comment: String = + s"""${OpenIDConnectToken._dbTableNameLC} table does not exist""".stripMargin + saveLog(name, commitId, isSuccessful, startDate, endDate, comment) + isSuccessful + } + } + def alterColumnRefreshToken(name: String): Boolean = { + DbFunction.tableExists(OpenIDConnectToken, (DB.use(DefaultConnectionIdentifier){ conn => conn})) match { + case true => + val startDate = System.currentTimeMillis() + val commitId: String = APIUtil.gitCommit + var isSuccessful = false + + val executedSql = + DbFunction.maybeWrite(true, Schemifier.infoF _, DB.use(DefaultConnectionIdentifier){ conn => conn}) { + () => "ALTER TABLE openidconnecttoken ALTER COLUMN refreshtoken type text;" + } + + val endDate = System.currentTimeMillis() + val comment: String = + s"""Executed SQL: + |$executedSql + |""".stripMargin + isSuccessful = true + saveLog(name, commitId, isSuccessful, startDate, endDate, comment) + isSuccessful + + case false => + val startDate = System.currentTimeMillis() + val commitId: String = APIUtil.gitCommit + val isSuccessful = false + val endDate = System.currentTimeMillis() + val comment: String = + s"""${OpenIDConnectToken._dbTableNameLC} table does not exist""".stripMargin + saveLog(name, commitId, isSuccessful, startDate, endDate, comment) + isSuccessful + } + } + +} diff --git a/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala b/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala index fbae6e21a..b12d47d0d 100644 --- a/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala +++ b/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala @@ -318,6 +318,7 @@ import net.liftweb.util.Helpers._ def getCurrentUserUsername: String = { getCurrentUser match { case Full(user) if user.provider.contains("google") => user.emailAddress + case Full(user) if user.provider.contains("yahoo") => user.emailAddress case Full(user) => user.name case _ => "" //TODO need more error handling for different user cases } diff --git a/obp-api/src/main/scala/code/token/MappedOpenIDConnectToken.scala b/obp-api/src/main/scala/code/token/MappedOpenIDConnectToken.scala index a0b3c298c..cf651db72 100644 --- a/obp-api/src/main/scala/code/token/MappedOpenIDConnectToken.scala +++ b/obp-api/src/main/scala/code/token/MappedOpenIDConnectToken.scala @@ -25,9 +25,9 @@ object MappedOpenIDConnectTokensProvider extends OpenIDConnectTokensProvider { class OpenIDConnectToken extends OpenIDConnectTokenTrait with LongKeyedMapper[OpenIDConnectToken] with IdPK with CreatedUpdated { def getSingleton: OpenIDConnectToken.type = OpenIDConnectToken - object AccessToken extends MappedString(this, 1024) + object AccessToken extends MappedText(this) object IDToken extends MappedText(this) - object RefreshToken extends MappedString(this, 1024) + object RefreshToken extends MappedText(this) object Scope extends MappedString(this, 250) object TokenType extends MappedString(this, 250) object ExpiresIn extends MappedLong(this)