Skip to content

Commit

Permalink
Support basic HTTP authentication (#2038)
Browse files Browse the repository at this point in the history
* Create a more generic and consistent way of providing authorization header

* Update engine/src/main/java/com/google/android/fhir/sync/HttpAuthenticator.kt

Co-authored-by: Omar Ismail <44980219+omarismail94@users.noreply.github.com>

* Update doc for HttpAuthenticator

---------

Co-authored-by: Omar Ismail <44980219+omarismail94@users.noreply.github.com>
  • Loading branch information
jingtang10 and omarismail94 authored Jun 29, 2023
1 parent 5dba2ec commit e8c3fcd
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ package com.google.android.fhir

import android.content.Context
import com.google.android.fhir.DatabaseErrorStrategy.UNSPECIFIED
import com.google.android.fhir.sync.Authenticator
import com.google.android.fhir.sync.DataSource
import com.google.android.fhir.sync.HttpAuthenticator
import com.google.android.fhir.sync.remote.HttpLogger
import org.hl7.fhir.r4.model.SearchParameter

Expand Down Expand Up @@ -145,11 +145,8 @@ data class ServerConfiguration(
val baseUrl: String,
/** A configuration to provide the network connection parameters. */
val networkConfiguration: NetworkConfiguration = NetworkConfiguration(),
/**
* An [Authenticator] for supplying any auth token that may be necessary to communicate with the
* server
*/
val authenticator: Authenticator? = null,
/** An [HttpAuthenticator] for providing HTTP authorization header. */
val authenticator: HttpAuthenticator? = null,
/** Logs the communication between the engine and the remote server. */
val httpLogger: HttpLogger = HttpLogger.NONE
)
Expand Down
29 changes: 0 additions & 29 deletions engine/src/main/java/com/google/android/fhir/sync/Authenticator.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2022 Google LLC
*
* 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.google.android.fhir.sync

import androidx.annotation.WorkerThread
import okhttp3.Credentials

/**
* Provides an authorization method for the HTTP requests FHIR Engine sends to the FHIR server.
*
* FHIR Engine does not handle user authentication. The application should handle user
* authentication and provide the appropriate authentication method so the HTTP requests FHIR Engine
* sends to the FHIR server contain the correct user information for the request to be
* authenticated.
*
* The implementation can provide different `HttpAuthenticationMethod`s at runtime. This is
* important if the authentication token expires or the user needs to re-authenticate.
*/
fun interface HttpAuthenticator {
fun getAuthenticationMethod(): HttpAuthenticationMethod
}

/**
* The HTTP authentication method to be used for generating HTTP authorization header.
*
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication.
*/
sealed interface HttpAuthenticationMethod {
/** @return The authorization header for the engine to make requests on user's behalf. */
@WorkerThread fun getAuthorizationHeader(): String

/** See https://datatracker.ietf.org/doc/html/rfc7617. */
data class Basic(val username: String, val password: String) : HttpAuthenticationMethod {
override fun getAuthorizationHeader() = Credentials.basic(username, password)
}

/** See https://datatracker.ietf.org/doc/html/rfc6750. */
data class Bearer(val token: String) : HttpAuthenticationMethod {
override fun getAuthorizationHeader() = "Bearer: $token"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package com.google.android.fhir.sync.remote

import com.google.android.fhir.NetworkConfiguration
import com.google.android.fhir.sync.Authenticator
import com.google.android.fhir.sync.HttpAuthenticator
import java.util.concurrent.TimeUnit
import okhttp3.Interceptor
import okhttp3.OkHttpClient
Expand All @@ -44,10 +44,10 @@ internal interface RetrofitHttpService : FhirHttpService {
private val baseUrl: String,
private val networkConfiguration: NetworkConfiguration
) {
private var authenticator: Authenticator? = null
private var authenticator: HttpAuthenticator? = null
private var httpLoggingInterceptor: HttpLoggingInterceptor? = null

fun setAuthenticator(authenticator: Authenticator?) = apply {
fun setAuthenticator(authenticator: HttpAuthenticator?) = apply {
this.authenticator = authenticator
}

Expand All @@ -69,12 +69,14 @@ internal interface RetrofitHttpService : FhirHttpService {
authenticator?.let {
addInterceptor(
Interceptor { chain: Interceptor.Chain ->
val accessToken = it.getAccessToken()
val request =
chain
.request()
.newBuilder()
.addHeader("Authorization", "Bearer $accessToken")
.addHeader(
"Authorization",
it.getAuthenticationMethod().getAuthorizationHeader()
)
.build()
chain.proceed(request)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2022 Google LLC
*
* 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.google.android.fhir.sync

import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class HttpAuthenticatorTest {
@Test
fun `should generate basic authentication header`() {
assertThat(HttpAuthenticationMethod.Basic("username", "password").getAuthorizationHeader())
.isEqualTo("Basic dXNlcm5hbWU6cGFzc3dvcmQ=")
}

@Test
fun `should generate bearer authentication token`() {
assertThat(HttpAuthenticationMethod.Bearer("token").getAuthorizationHeader())
.isEqualTo("Bearer: token")
}
}

0 comments on commit e8c3fcd

Please sign in to comment.