Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ nb-configuration.xml

# Local environment
.env
.env.json
env.json
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ mvn clean package -Pnative
Running AWS SAM locally

```bash
# Make sure you have your "sam/env.json" file in place
sam local invoke --template sam/sam.native.yaml --event sam/event.json --env-vars sam/env.json
sam local invoke --template sam/sam.native.yaml --env-vars sam/env.json --event sam/events/authorize.json
```

```bash
sam local invoke --template sam/sam.native.yaml --env-vars sam/env.json --event sam/events/callback.json
```
25 changes: 12 additions & 13 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,6 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.quarkiverse.googlecloudservices</groupId>
<artifactId>quarkus-google-cloud-services-bom</artifactId>
<version>${quarkus.google-cloud-services.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
Expand Down Expand Up @@ -81,22 +74,25 @@
<groupId>org.jboss.logging</groupId>
<artifactId>commons-logging-jboss-logging</artifactId>
</dependency>

<!-- additional deps -->
<dependency>
<groupId>io.quarkiverse.googlecloudservices</groupId>
<artifactId>quarkus-google-cloud-secret-manager</artifactId>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-config-yaml</artifactId>
</dependency>

<!-- additional deps -->
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-oauth2</artifactId>
<version>${google-api-services-oauth2.version}</version>
</dependency>
<dependency>
<groupId>com.google.http-client</groupId>
<artifactId>google-http-client</artifactId>
</dependency>
</dependencies>

<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
Expand Down Expand Up @@ -208,6 +204,7 @@
</plugin>
</plugins>
</build>

<profiles>
<profile>
<id>native</id>
Expand All @@ -221,8 +218,10 @@
<quarkus.package.type>native</quarkus.package.type>
<quarkus.native.container-build>true</quarkus.native.container-build>
<quarkus.native.builder-image>quay.io/quarkus/ubi-quarkus-mandrel:22.2-java11</quarkus.native.builder-image>
<quarkus.native.native-image-xmx>2g</quarkus.native.native-image-xmx>
<quarkus.native.native-image-xmx>8g</quarkus.native.native-image-xmx>
<quarkus.native.additional-build-args>
-H:+RemoveSaturatedTypeFlows,\
-H:+PrintAnalysisStatistics,\
-H:ResourceConfigurationFiles=resources-config.json,\
-H:ReflectionConfigurationFiles=reflection-config.json,\
-H:EnableURLProtocols=http\,https,\
Expand Down
5 changes: 0 additions & 5 deletions sam/env.json

This file was deleted.

7 changes: 5 additions & 2 deletions sam/env.json.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"function": {
"GOOGLE_CLIENT_SECRET": "secret"
"GoogleAuthNative": {
"LOG_LEVEL": "DEBUG",
"GOOGLE_CLIENT_ID": "",
"GOOGLE_CLIENT_SECRET": "",
"GOOGLE_CLIENT_REDIRECT_URI": "http://localhost:8080/callback"
}
}
2 changes: 1 addition & 1 deletion sam/event.json → sam/events/authorize.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"path": "/",
"path": "/authorize",
"httpMethod": "GET"
}
4 changes: 4 additions & 0 deletions sam/events/callback.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"path": "/callback?state=some-state&code=4%2F0ARtbsJq5sVmW60sVxVWpKemSD3uUpbYjkAEpDIEU7L9bXZ1xGB1UQR_UI5zaNEM-g_xJgw&scope=email+profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&authuser=0&prompt=consent",
"httpMethod": "GET"
}
5 changes: 4 additions & 1 deletion sam/sam.native.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ Resources:
Environment:
Variables:
DISABLE_SIGNAL_HANDLERS: true
GOOGLE_CLIENT_SECRET: secret
LOG_LEVEL: null
GOOGLE_CLIENT_ID: null
GOOGLE_CLIENT_SECRET: null
GOOGLE_CLIENT_REDIRECT_URI: null
Events:
GetResource:
Type: Api
Expand Down
10 changes: 10 additions & 0 deletions src/main/kotlin/com/acme/AuthUser.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.acme

import com.google.api.services.oauth2.model.Userinfo
import io.quarkus.runtime.annotations.RegisterForReflection

@RegisterForReflection
data class AuthUser(
val refreshToken: String,
val userInfo: Userinfo
)
109 changes: 109 additions & 0 deletions src/main/kotlin/com/acme/GoogleOAuthClient.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package com.acme

import com.google.api.client.auth.oauth2.BearerToken
import com.google.api.client.auth.oauth2.Credential
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeRequestUrl
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets
import com.google.api.client.http.apache.v2.ApacheHttpTransport
import com.google.api.client.json.JsonFactory
import com.google.api.client.json.gson.GsonFactory
import com.google.api.services.oauth2.Oauth2
import com.google.api.services.oauth2.Oauth2Scopes
import java.net.URL
import javax.enterprise.context.ApplicationScoped
import org.slf4j.Logger
import org.slf4j.LoggerFactory

@ApplicationScoped
class GoogleOAuthClient(
private val properties: GoogleOAuthProperties
) {

private val logger: Logger = LoggerFactory.getLogger(this.javaClass)

private val scopes = setOf(
// For basic user information
Oauth2Scopes.OPENID,
Oauth2Scopes.USERINFO_PROFILE,
Oauth2Scopes.USERINFO_EMAIL
)

private fun googleAuthorizationCodeFlow(): GoogleAuthorizationCodeFlow {
val jsonFactory: JsonFactory = GsonFactory.getDefaultInstance()
val details = GoogleClientSecrets.Details()

details.clientId = properties.clientId()
details.clientSecret = properties.clientSecret()

val clientSecrets = GoogleClientSecrets()
clientSecrets.installed = details

// Create builder
val builder = GoogleAuthorizationCodeFlow
.Builder(ApacheHttpTransport(), jsonFactory, clientSecrets, scopes)

return builder
.setAccessType("offline")
.setApprovalPrompt("force")
.build()
}

fun createAuthUrl(state: String): URL {
logger.debug("Creating a new authentication URL")
val flow = googleAuthorizationCodeFlow()

val codeRequestUrl: GoogleAuthorizationCodeRequestUrl = flow
.newAuthorizationUrl()
.setRedirectUri(properties.redirectUri())
.setState(state)

logger.info("Entries {}", codeRequestUrl.entries.size)
logger.info("Empty in native builds: {}", codeRequestUrl.buildRelativeUrl())

return codeRequestUrl.toURL()
}

fun authenticate(authorizationCode: String): AuthUser {
logger.debug("Fetching a new refresh token")
val flow = googleAuthorizationCodeFlow()

val codeTokenRequest: GoogleAuthorizationCodeTokenRequest = flow
.newTokenRequest(authorizationCode)
.setRedirectUri(properties.redirectUri())

logger.warn("Entries {}", codeTokenRequest.entries.size)
codeTokenRequest.entries.forEach { e ->
logger.info("${e.key} = ${e.value}")
}

logger.info("Let's execute the request")
val token = codeTokenRequest.execute()

// Let's save the refresh token in DynamoDB so that every instance will
// be able to reuse it
val httpTransport = ApacheHttpTransport()
val jsonFactory: JsonFactory = GsonFactory.getDefaultInstance()

val httpRequestInitializer = Credential.Builder(BearerToken.authorizationHeaderAccessMethod()).build()
httpRequestInitializer.accessToken = token.accessToken
logger.info("httpRequestInitializer.accessToken {}", httpRequestInitializer.accessToken)

val oauth2: Oauth2 = Oauth2
.Builder(httpTransport, jsonFactory, httpRequestInitializer)
.setApplicationName(properties.applicationName())
.build()

val get = oauth2
.userinfo()
.get()

val userInfo = get.execute()

return AuthUser(
refreshToken = token.refreshToken,
userInfo = userInfo
)
}
}
17 changes: 17 additions & 0 deletions src/main/kotlin/com/acme/GoogleOAuthProperties.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.acme

import io.quarkus.runtime.annotations.RegisterForReflection
import io.smallrye.config.ConfigMapping

@RegisterForReflection
@ConfigMapping(prefix = GoogleOAuthProperties.PREFIX)
interface GoogleOAuthProperties {
fun applicationName(): String
fun clientId(): String
fun clientSecret(): String
fun redirectUri(): String

companion object {
const val PREFIX = "pws-google"
}
}
63 changes: 0 additions & 63 deletions src/main/kotlin/com/acme/GreetingResource.kt

This file was deleted.

54 changes: 54 additions & 0 deletions src/main/kotlin/com/acme/ReflectionConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.acme

import io.quarkus.runtime.annotations.RegisterForReflection

@Suppress("unused")
@RegisterForReflection(
targets = [
// google-http-client
com.google.api.client.http.HttpHeaders::class,
com.google.api.client.json.rpc2.JsonRpcRequest::class,
com.google.api.client.json.webtoken.JsonWebSignature::class,
com.google.api.client.json.webtoken.JsonWebToken.Header::class,
// google-api-client
com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeRequestUrl::class,
com.google.api.client.googleapis.auth.oauth2.GoogleBrowserClientRequestUrl::class,
com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets::class,
com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets.Details::class,
com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload::class,
com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse::class,
com.google.api.client.googleapis.json.GoogleJsonError.ErrorInfo::class,
com.google.api.client.googleapis.json.GoogleJsonError.Details::class,
com.google.api.client.googleapis.json.GoogleJsonError.ParameterViolations::class,
com.google.api.client.googleapis.json.GoogleJsonError::class,
com.google.api.client.googleapis.json.GoogleJsonErrorContainer::class,
com.google.api.client.googleapis.mtls.ContextAwareMetadataJson::class,
// google-api-services-oauth2-v2
com.google.api.services.oauth2.model.Userinfo::class,
com.google.api.services.oauth2.model.Tokeninfo::class,
com.google.api.services.oauth2.Oauth2.Tokeninfo::class,
com.google.api.services.oauth2.Oauth2.Userinfo.Get::class,
com.google.api.services.oauth2.Oauth2.Userinfo.V2.Me::class,
com.google.api.services.oauth2.Oauth2.Userinfo.V2.Me.Get::class,
// google-oauth-client
com.google.api.client.auth.oauth.OAuthAuthorizeTemporaryTokenUrl::class,
com.google.api.client.auth.oauth.OAuthCallbackUrl::class,
com.google.api.client.auth.oauth.OAuthCredentialsResponse::class,
com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl::class,
com.google.api.client.auth.oauth2.AuthorizationCodeResponseUrl::class,
com.google.api.client.auth.oauth2.AuthorizationCodeTokenRequest::class,
com.google.api.client.auth.oauth2.AuthorizationRequestUrl::class,
com.google.api.client.auth.oauth2.PasswordTokenRequest::class,
com.google.api.client.auth.oauth2.RefreshTokenRequest::class,
com.google.api.client.auth.oauth2.TokenErrorResponse::class,
com.google.api.client.auth.oauth2.TokenRequest::class,
com.google.api.client.auth.oauth2.TokenResponse::class,
com.google.api.client.auth.openidconnect.IdToken.Payload::class,
com.google.api.client.auth.openidconnect.IdTokenResponse::class
// @TODO: This must be fixed first
// https://github.com/googleapis/google-oauth-java-client/issues/947
// com.google.api.client.auth.openidconnect.IdTokenVerifier.PublicKeyLoader.JsonWebKeySet::class
// com.google.api.client.auth.openidconnect.IdTokenVerifier.PublicKeyLoader.JsonWebKey::class
]
)
class ReflectionConfig
Loading