-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: new SDK core integration (#114)
Co-authored-by: mohnoor94 <[email protected]> Co-authored-by: Omar Aljarrah <[email protected]>
- Loading branch information
1 parent
c8b1b83
commit 3fcec30
Showing
121 changed files
with
3,704 additions
and
3,560 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,12 +1,3 @@ | ||
plugins { | ||
id 'org.jetbrains.kotlin.jvm' version '2.0.21' | ||
} | ||
|
||
kotlin { | ||
jvmToolchain(8) | ||
} | ||
|
||
dependencies { | ||
// Add apollo-compiler as a dependency | ||
implementation("com.apollographql.apollo:apollo-compiler:4.1.0") | ||
implementation 'com.apollographql.apollo:apollo-compiler:4.1.0' | ||
} |
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
92 changes: 92 additions & 0 deletions
92
...kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationInterceptor.kt
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,92 @@ | ||
/* | ||
* Copyright (C) 2024 Expedia, Inc. | ||
* | ||
* Licensed 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 com.expediagroup.sdk.core.authentication.bearer | ||
|
||
import com.expediagroup.sdk.core.http.Request | ||
import com.expediagroup.sdk.core.http.Response | ||
import com.expediagroup.sdk.core.interceptor.Interceptor | ||
import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupAuthException | ||
import java.io.IOException | ||
|
||
/** | ||
* An interceptor that handles bearer token-based authentication for HTTP requests. | ||
* | ||
* This interceptor ensures that an up-to-date bearer token is added to the `Authorization` header of each HTTP request. | ||
* It manages token expiration and re-authentication automatically. If the token is about to expire, the interceptor | ||
* synchronously refreshes it before proceeding with the request. | ||
* | ||
* @param authManager The [BearerAuthenticationManager] used for making authentication requests execution and parsing. | ||
*/ | ||
class BearerAuthenticationInterceptor( | ||
private val authManager: BearerAuthenticationManager | ||
) : Interceptor { | ||
|
||
private val lock = Any() | ||
|
||
/** | ||
* Intercepts the HTTP request, adding a bearer token to the `Authorization` header. | ||
* | ||
* This method checks if the token needs to be refreshed and does so if necessary. It excludes | ||
* requests targeting the `authUrl` from this behavior to avoid recursive authentication requests. | ||
* | ||
* @param chain The [Interceptor.Chain] responsible for managing the request and its progression. | ||
* @return The [Response] resulting from the executed request. | ||
* @throws ExpediaGroupAuthException If authentication fails due to invalid credentials or server errors | ||
*/ | ||
@Throws(ExpediaGroupAuthException::class) | ||
override fun intercept(chain: Interceptor.Chain): Response { | ||
val request = chain.request() | ||
|
||
if (isAuthenticationRequest(request)) { | ||
return chain.proceed(request) | ||
} | ||
|
||
ensureValidAuthentication() | ||
|
||
val authorizedRequest = request.newBuilder() | ||
.addHeader("Authorization", authManager.getAuthorizationHeaderValue()) | ||
.build() | ||
|
||
return chain.proceed(authorizedRequest) | ||
} | ||
|
||
/** | ||
* Checks if the given request is for authentication. | ||
*/ | ||
private fun isAuthenticationRequest(request: Request): Boolean = request.url.toString() == authManager.authUrl | ||
|
||
/** | ||
* Ensures there is a valid authentication token available. | ||
* If needed, authenticates under a synchronization lock to prevent multiple simultaneous authentications. | ||
* | ||
* @throws ExpediaGroupAuthException If authentication fails | ||
*/ | ||
@Throws(ExpediaGroupAuthException::class) | ||
private fun ensureValidAuthentication() { | ||
try { | ||
if (authManager.isTokenAboutToExpire()) { | ||
synchronized(lock) { | ||
if (authManager.isTokenAboutToExpire()) { | ||
authManager.authenticate() | ||
} | ||
} | ||
} | ||
} catch (e: IOException) { | ||
throw ExpediaGroupAuthException("Failed to authenticate", e) | ||
} | ||
} | ||
} |
153 changes: 153 additions & 0 deletions
153
...ain/kotlin/com/expediagroup/sdk/core/authentication/bearer/BearerAuthenticationManager.kt
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,153 @@ | ||
/* | ||
* Copyright (C) 2024 Expedia, Inc. | ||
* | ||
* Licensed 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 com.expediagroup.sdk.core.authentication.bearer | ||
|
||
import com.expediagroup.sdk.core.authentication.common.AuthenticationManager | ||
import com.expediagroup.sdk.core.authentication.common.Credentials | ||
import com.expediagroup.sdk.core.client.Transport | ||
import com.expediagroup.sdk.core.http.MediaType | ||
import com.expediagroup.sdk.core.http.Request | ||
import com.expediagroup.sdk.core.http.RequestBody | ||
import com.expediagroup.sdk.core.http.Response | ||
import com.expediagroup.sdk.core.logging.common.LoggerDecorator | ||
import com.expediagroup.sdk.core.logging.common.RequestLogger | ||
import com.expediagroup.sdk.core.logging.common.ResponseLogger | ||
import com.expediagroup.sdk.core.model.exception.client.ExpediaGroupResponseParsingException | ||
import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupAuthException | ||
import com.expediagroup.sdk.core.model.exception.service.ExpediaGroupNetworkException | ||
import org.slf4j.LoggerFactory | ||
|
||
/** | ||
* Manages bearer token authentication for HTTP requests. | ||
* | ||
* The `BearerAuthenticationManager` handles the lifecycle of bearer tokens, including retrieval, storage, | ||
* and validation. It interacts with an authentication server to fetch tokens using client credentials, | ||
* ensures tokens are refreshed when necessary, and provides them in the required format for authorization headers. | ||
* | ||
* @param transport The [Transport] used to execute authentication requests. | ||
* @param authUrl The URL of the authentication server's endpoint to obtain bearer tokens. | ||
* @param credentials The [Credentials] containing the client key and secret used for authentication. | ||
*/ | ||
class BearerAuthenticationManager( | ||
val authUrl: String, | ||
private val transport: Transport, | ||
private val credentials: Credentials | ||
) : AuthenticationManager { | ||
|
||
@Volatile | ||
private var bearerTokenStorage = BearerTokenStorage.empty | ||
|
||
/** | ||
* Initiates authentication to obtain a new bearer token. | ||
* | ||
* This method sends a request to the authentication server, parses the response, and | ||
* stores the token for future use. | ||
* | ||
* @throws ExpediaGroupAuthException If the authentication request fails. | ||
* @throws ExpediaGroupResponseParsingException If the response cannot be parsed. | ||
*/ | ||
@Throws(ExpediaGroupAuthException::class, ExpediaGroupResponseParsingException::class) | ||
override fun authenticate() { | ||
clearAuthentication() | ||
.let { | ||
buildAuthenticationRequest().also { | ||
RequestLogger.log(logger, it, "Authentication") | ||
} | ||
}.let { | ||
executeAuthenticationRequest(it).also { | ||
ResponseLogger.log(logger, it, "Authentication") | ||
} | ||
}.let { | ||
TokenResponse.parse(it) | ||
}.also { | ||
storeToken(it) | ||
} | ||
} | ||
|
||
/** | ||
* Checks if the current bearer token is about to expire and needs renewal. | ||
* | ||
* @return `true` if the token is near expiration, `false` otherwise. | ||
*/ | ||
fun isTokenAboutToExpire(): Boolean = run { | ||
bearerTokenStorage.isAboutToExpire() | ||
} | ||
|
||
/** | ||
* Clears the stored authentication token. | ||
* | ||
* This method resets the internal token storage, effectively invalidating the current session. | ||
*/ | ||
override fun clearAuthentication() = run { | ||
bearerTokenStorage = BearerTokenStorage.empty | ||
} | ||
|
||
/** | ||
* Retrieves the stored token formatted as an `Authorization` header value. | ||
* | ||
* @return The token in the format `Bearer <token>` for use in HTTP headers. | ||
*/ | ||
fun getAuthorizationHeaderValue(): String = run { | ||
bearerTokenStorage.getAuthorizationHeaderValue() | ||
} | ||
|
||
/** | ||
* Creates an HTTP request to fetch a new bearer token from the authentication server. | ||
* | ||
* @return A [Request] object configured with the necessary headers and parameters. | ||
*/ | ||
private fun buildAuthenticationRequest(): Request = run { | ||
Request.Builder() | ||
.url(authUrl) | ||
.method("POST", RequestBody.create(mapOf("grant_type" to "client_credentials"))) | ||
.header("Authorization", credentials.encodeBasic()) | ||
.header("Content-Type", MediaType.APPLICATION_FORM_URLENCODED.toString()) | ||
.build() | ||
} | ||
|
||
/** | ||
* Executes the authentication request and validates the response. | ||
* | ||
* @param request The [Request] object to be executed. | ||
* @return The [Response] received from the server. | ||
* @throws ExpediaGroupAuthException If the server responds with an error. | ||
*/ | ||
@Throws(ExpediaGroupAuthException::class, ExpediaGroupNetworkException::class) | ||
private fun executeAuthenticationRequest(request: Request): Response = run { | ||
transport.execute(request).apply { | ||
if (!this.isSuccessful) { | ||
throw ExpediaGroupAuthException(this.code, "Authentication failed") | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Stores the retrieved token in internal storage for subsequent use. | ||
* | ||
* @param tokenResponse The [TokenResponse] containing the token and its expiration time. | ||
*/ | ||
private fun storeToken(tokenResponse: TokenResponse) = run { | ||
bearerTokenStorage = BearerTokenStorage.create( | ||
accessToken = tokenResponse.accessToken, | ||
expiresIn = tokenResponse.expiresIn | ||
) | ||
} | ||
|
||
private companion object { | ||
private val logger = LoggerDecorator(LoggerFactory.getLogger(this::class.java.enclosingClass)) | ||
} | ||
} |
Oops, something went wrong.